using System; using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; using static BulletHellTemplate.FirebaseManager; namespace BulletHellTemplate { /// /// Handles saving and retrieving player local data such as name, icon, frame, and character upgrades using PlayerPrefs. /// public class PlayerSave : MonoBehaviour { //Account private const string PlayerNamePrefix = "PLAYERNAME_"; private const string PlayerIconPrefix = "PLAYERICON_"; private const string PlayerFramePrefix = "PLAYERFRAME_"; private const string PlayerAccountLevelPrefix = "PLAYERACCOUNTLEVEL_"; private const string PlayerAccountCurrentExpPrefix = "PLAYERACCOUNTCURRENTEXP_"; private const string TutorialIsDonePrefix = "TUTORIALISDONE_"; //Character private const string PlayerSelectedCharacterPrefix = "PLAYERSELECTEDCHARACTER_"; private const string PlayerFavouriteCharacterPrefix = "PLAYERFAVOURITECHARACTER_"; private const string CharacterMasteryLevelPrefix = "CHARACTERMASTERYLEVEL_"; private const string CharacterMasteryCurrentExpPrefix = "CHARACTERMASTERYCURRENTEXP_"; private const string CharacterLevelPrefix = "CHARACTERLEVEL_"; private const string CharacterCurrentExpPrefix = "CHARACTERCURRENTEXP_"; private const string CharacterSlotPrefix = "ITEMSLOT_"; private const string ItemLevelPrefix = "ITEMLEVEL_"; private const string CharacterUpgradeLevelPrefix = "CHARACTERUPGRADELEVEL_"; private const string CharacterSkinPrefix = "CHARACTERSKIN_"; private const string CharacterUnlockedSkinsPrefix = "CHARACTERUNLOCKEDSKINS_"; //Progress private const string UnlockedMapsKey = "UNLOCKED_MAPS"; private const string QuestProgressPrefix = "QUEST_PROGRESS_"; private const string QuestCompletionPrefix = "QUEST_COMPLETED_"; private const string UsedCouponsKey = "USED_COUPONS_"; private const string DailyRewardsKey = "LOCAL_DAILYREWARDS_DATA"; private const string NewPlayerRewardsKey = "LOCAL_NEWPLAYERREWARDS_DATA"; private const string NextDailyResetKey = "NEXTDAILYRESETTIME"; private static bool isLoading = false; public static void SetLoadingState(bool state) { isLoading = state; } #region Account Functions /// /// Sets the player's name in PlayerPrefs. /// /// The name of the player. public static void SetPlayerName(string playerName) { PlayerPrefs.SetString(PlayerNamePrefix, playerName); PlayerPrefs.Save(); } /// /// Gets the player's name from PlayerPrefs. /// /// The name of the player. public static string GetPlayerName() { return PlayerPrefs.GetString(PlayerNamePrefix); } /// /// Sets the player's icon in PlayerPrefs. /// /// The icon of the player. public static void SetPlayerIcon(string playerIcon) { PlayerPrefs.SetString(PlayerIconPrefix, playerIcon); PlayerPrefs.Save(); } /// /// Gets the player's icon from PlayerPrefs. /// /// The icon of the player. public static string GetPlayerIcon() { return PlayerPrefs.GetString(PlayerIconPrefix); } /// /// Sets the player's frame in PlayerPrefs. /// /// The frame of the player. public static void SetPlayerFrame(string playerFrame) { PlayerPrefs.SetString(PlayerFramePrefix, playerFrame); PlayerPrefs.Save(); } /// /// Gets the player's frame from PlayerPrefs. /// /// The frame of the player. public static string GetPlayerFrame() { return PlayerPrefs.GetString(PlayerFramePrefix); } public static void SetAccountLevel(int level) { PlayerPrefs.SetInt(PlayerAccountLevelPrefix, level); PlayerPrefs.Save(); } public static int GetAccountLevel() { return PlayerPrefs.GetInt(PlayerAccountLevelPrefix, 1); } public static void SetAccountCurrentExp(int currentExp) { PlayerPrefs.SetInt(PlayerAccountCurrentExpPrefix, currentExp); PlayerPrefs.Save(); } public static int GetAccountCurrentExp() { return PlayerPrefs.GetInt(PlayerAccountCurrentExpPrefix, 0); } #endregion #region Characters Functions /// /// Sets the selected character's ID in PlayerPrefs. /// /// The ID of the selected character. public static void SetSelectedCharacter(int characterId) { PlayerPrefs.SetInt(PlayerSelectedCharacterPrefix, characterId); PlayerPrefs.Save(); } /// /// Gets the selected character's ID from PlayerPrefs. /// If no character is selected, returns the first unlocked character's ID. /// /// The ID of the selected or first unlocked character. public static int GetSelectedCharacter() { if (PlayerPrefs.HasKey(PlayerSelectedCharacterPrefix)) { return PlayerPrefs.GetInt(PlayerSelectedCharacterPrefix); } else { if (GameInstance.Singleton != null && GameInstance.Singleton.characterData != null) { foreach (CharacterData character in GameInstance.Singleton.characterData) { if (character.CheckUnlocked) { return character.characterId; } } } Debug.LogError("No unlocked characters available to select."); return -1; } } /// /// Sets the favourite character's ID in PlayerPrefs. /// /// The ID of the favourite character. public static void SetFavouriteCharacter(int characterId) { PlayerPrefs.SetInt(PlayerFavouriteCharacterPrefix, characterId); PlayerPrefs.Save(); } /// /// Gets the favourite character's ID from PlayerPrefs. /// If no favourite character is set, returns 0. /// /// The ID of the favourite character or 0 if not set. public static int GetFavouriteCharacter() { if (PlayerPrefs.HasKey(PlayerFavouriteCharacterPrefix)) { return PlayerPrefs.GetInt(PlayerFavouriteCharacterPrefix); } return 0; } /// /// Sets the level for the specified character. /// /// The character identifier. /// The level to set. public static void SetCharacterLevel(int characterId, int level) { PlayerPrefs.SetInt(CharacterLevelPrefix + characterId, level); PlayerPrefs.Save(); } /// /// Gets the level of the specified character. /// Returns 1 if the key does not exist. /// /// The character identifier. /// The character level. public static int GetCharacterLevel(int characterId) { return PlayerPrefs.GetInt(CharacterLevelPrefix + characterId, 1); } /// /// Sets the current experience for the specified character. /// /// The character identifier. /// The current experience to set. public static void SetCharacterCurrentExp(int characterId, int currentExp) { PlayerPrefs.SetInt(CharacterCurrentExpPrefix + characterId, currentExp); PlayerPrefs.Save(); } /// /// Gets the current experience of the specified character. /// Returns 0 if the key does not exist. /// /// The character identifier. /// The current experience. public static int GetCharacterCurrentExp(int characterId) { return PlayerPrefs.GetInt(CharacterCurrentExpPrefix + characterId, 0); } /// /// Sets the mastery level for the specified character. /// /// The character identifier. /// The mastery level to set. public static void SetCharacterMasteryLevel(int characterId, int masteryLevel) { PlayerPrefs.SetInt(CharacterMasteryLevelPrefix + characterId, masteryLevel); PlayerPrefs.Save(); } /// /// Gets the mastery level of the specified character. /// Returns 0 if the key does not exist. /// /// The character identifier. /// The mastery level. public static int GetCharacterMasteryLevel(int characterId) { return PlayerPrefs.GetInt(CharacterMasteryLevelPrefix + characterId, 0); } /// /// Sets the current mastery experience for the specified character. /// /// The character identifier. /// The current mastery experience to set. public static void SetCharacterCurrentMasteryExp(int characterId, int masteryCurrentExp) { PlayerPrefs.SetInt(CharacterMasteryCurrentExpPrefix + characterId, masteryCurrentExp); PlayerPrefs.Save(); } /// /// Gets the current mastery experience of the specified character. /// Returns 0 if the key does not exist. /// /// The character identifier. /// The current mastery experience. public static int GetCharacterCurrentMasteryExp(int characterId) { return PlayerPrefs.GetInt(CharacterMasteryCurrentExpPrefix + characterId, 0); } /// /// Sets the unique GUID of the item equipped in a specific slot of a given character. /// Example key: "CharSlot_{characterId}_{slotName}" /// /// The ID of the character. /// The slot name (e.g. "WeaponSlot"). /// The unique ID of the item, or empty to unequip. public static void SetCharacterSlotItem(int characterId, string slotName, string uniqueItemGuid) { string key = $"{CharacterSlotPrefix}{characterId}_{slotName}"; PlayerPrefs.SetString(key, uniqueItemGuid); PlayerPrefs.Save(); } /// /// Returns the unique GUID of the item equipped in the specified slot for the given character. /// Returns an empty string if none is equipped. /// /// The ID of the character. /// The slot name. /// The unique item GUID, or empty if none. public static string GetCharacterSlotItem(int characterId, string slotName) { string key = $"{CharacterSlotPrefix}{characterId}_{slotName}"; return PlayerPrefs.GetString(key, string.Empty); } /// /// Saves the upgrade level for a purchased item identified by its unique GUID. /// Example key: "ItemLevel_{uniqueItemGuid}" /// /// Unique identifier of the purchased item. /// Current upgrade level. public static void SetItemUpgradeLevel(string uniqueItemGuid, int level) { string key = $"{ItemLevelPrefix}{uniqueItemGuid}"; PlayerPrefs.SetInt(key, level); PlayerPrefs.Save(); } /// /// Retrieves the upgrade level for a purchased item identified by its unique GUID. /// Returns 0 if not found. /// /// Unique identifier of the purchased item. /// Upgrade level if present, otherwise 0. public static int GetItemUpgradeLevel(string uniqueItemGuid) { string key = $"{ItemLevelPrefix}{uniqueItemGuid}"; return PlayerPrefs.GetInt(key, 0); } public static void SetCharacterSkin(int characterId, int skinIndex) { PlayerPrefs.SetInt(CharacterSkinPrefix + characterId, skinIndex); PlayerPrefs.Save(); } public static int GetCharacterSkin(int characterId) { string key = CharacterSkinPrefix + characterId; return PlayerPrefs.HasKey(key) ? PlayerPrefs.GetInt(key) : -1; } /// /// Saves the unlocked skins for a character locally. /// /// Unique identifier for the character. /// List of unlocked skin indices. public static void SaveCharacterUnlockedSkins(int characterId, List unlockedSkins) { UnlockedSkinsData data = new UnlockedSkinsData { unlockedSkins = unlockedSkins }; string json = JsonUtility.ToJson(data); PlayerPrefs.SetString(CharacterUnlockedSkinsPrefix + characterId, json); PlayerPrefs.Save(); } /// /// Loads the unlocked skins for a character from local storage. /// /// Unique identifier for the character. /// List of unlocked skin indices. Returns an empty list if no data is found. public static List LoadCharacterUnlockedSkins(int characterId) { string key = CharacterUnlockedSkinsPrefix + characterId; if (PlayerPrefs.HasKey(key)) { string json = PlayerPrefs.GetString(key); UnlockedSkinsData data = JsonUtility.FromJson(json); if (data != null && data.unlockedSkins != null) { return data.unlockedSkins; } } return new List(); } /// /// Saves the initial upgrade level (0) for the specified character's stat type. /// /// The character identifier. /// The stat type. public static void SaveInitialCharacterUpgradeLevel(int characterId, StatType statType) { string key = $"{CharacterUpgradeLevelPrefix}{characterId}_{statType}"; PlayerPrefs.SetInt(key, 0); PlayerPrefs.Save(); } public static void SaveCharacterUpgradeLevel(int characterId, StatType statType, int level) { string key = $"{CharacterUpgradeLevelPrefix}{characterId}_{statType}"; PlayerPrefs.SetInt(key, level); PlayerPrefs.Save(); if (!isLoading && BackendManager.Singleton.CheckInitialized()) { BackendManager.Singleton.StartCoroutine(SaveCharacterUpgradesAsyncCoroutine(characterId)); } } private static IEnumerator SaveCharacterUpgradesAsyncCoroutine(int characterId) { var upgrades = LoadAllCharacterUpgradeLevels(characterId); var task = BackendManager.Singleton.SaveCharacterUpgradesAsync(characterId.ToString(), upgrades); yield return new WaitUntil(() => task.IsCompleted); if (task.Exception != null) { Debug.LogError($"Failed to save character upgrades for {characterId}: {task.Exception}"); } } public static int LoadCharacterUpgradeLevel(int characterId, StatType statType) { string key = $"{CharacterUpgradeLevelPrefix}{characterId}_{statType}"; return PlayerPrefs.GetInt(key, 0); } public static Dictionary LoadAllCharacterUpgradeLevels(int characterId) { Dictionary upgradeLevels = new Dictionary(); foreach (StatType statType in System.Enum.GetValues(typeof(StatType))) { upgradeLevels[statType] = LoadCharacterUpgradeLevel(characterId, statType); } return upgradeLevels; } public static void ResetCharacterUpgrades(int characterId) { foreach (StatType statType in System.Enum.GetValues(typeof(StatType))) { string key = $"{CharacterUpgradeLevelPrefix}{characterId}_{statType}"; if (PlayerPrefs.HasKey(key)) { PlayerPrefs.DeleteKey(key); } } PlayerPrefs.Save(); } #endregion /// /// Clears the unlocked maps in PlayerPrefs. /// public static void ClearUnlockedMaps() { PlayerPrefs.DeleteKey(UnlockedMapsKey); PlayerPrefs.Save(); Debug.Log("Local unlocked maps have been cleared."); } /// /// Sets the unlocked maps in PlayerPrefs and optionally saves them to the database if initialized. /// /// A list of map IDs that are unlocked. public static void SetUnlockedMaps(List unlockedMapIds) { string mapIdsString = string.Join(",", unlockedMapIds); PlayerPrefs.SetString(UnlockedMapsKey, mapIdsString); PlayerPrefs.Save(); if (BackendManager.Singleton.CheckInitialized()) { BackendManager.Singleton.SaveUnlockedMapsAsync().ContinueWith(task => { if (task.IsFaulted) { Debug.LogError("Failed to save unlocked maps to backend: " + task.Exception); } }); } } /// /// Gets the unlocked maps from PlayerPrefs. /// /// A list of map IDs that are unlocked. public static List GetUnlockedMaps() { string mapIdsString = PlayerPrefs.GetString(UnlockedMapsKey, ""); if (string.IsNullOrEmpty(mapIdsString)) { return new List(); } string[] mapIdsArray = mapIdsString.Split(','); List unlockedMapIds = new List(); foreach (string id in mapIdsArray) { if (int.TryParse(id, out int mapId)) { unlockedMapIds.Add(mapId); } } return unlockedMapIds; } /// /// Saves the progress of a quest both locally and in backend if initialized. /// /// The ID of the quest. /// The progress value to save. public static void SaveQuestProgress(int questId, int progress) { // Save the quest progress locally PlayerPrefs.SetInt($"{QuestProgressPrefix}{questId}", progress); PlayerPrefs.Save(); if (BackendManager.Singleton.CheckInitialized()) { BackendManager.Singleton.StartCoroutine(SaveQuestProgressToBackendCoroutine(questId, progress)); } } /// /// Coroutine to save quest progress to Backend. /// /// The ID of the quest. /// The progress value to save. /// IEnumerator for coroutine execution. private static IEnumerator SaveQuestProgressToBackendCoroutine(int questId, int progress) { var task = BackendManager.Singleton.SaveQuestProgressAsync(questId, progress); yield return new WaitUntil(() => task.IsCompleted); if (task.Exception != null) { Debug.LogError($"Failed to save quest progress for {questId}: {task.Exception}"); } } /// /// Loads the progress of a quest. /// /// The ID of the quest. /// The saved progress value. public static int LoadQuestProgress(int questId) { return PlayerPrefs.GetInt($"{QuestProgressPrefix}{questId}", 0); // Default to 0 if not set } /// /// Saves the completion status of a quest both locally and in Backend if initialized. /// /// The ID of the quest. public static void SaveQuestCompletion(int questId) { // Save the quest completion status locally PlayerPrefs.SetInt($"{QuestCompletionPrefix}{questId}", 1); PlayerPrefs.Save(); if (BackendManager.Singleton.CheckInitialized()) { BackendManager.Singleton.StartCoroutine(SaveQuestCompletionToBackendCoroutine(questId)); } } /// /// Coroutine to save quest completion to Backend. /// /// The ID of the quest. /// IEnumerator for coroutine execution. private static IEnumerator SaveQuestCompletionToBackendCoroutine(int questId) { var task = BackendManager.Singleton.SaveQuestCompletionAsync(questId); yield return new WaitUntil(() => task.IsCompleted); if (task.Exception != null) { Debug.LogError($"Failed to save quest completion for {questId}: {task.Exception}"); } } /// /// Checks if a quest is completed. /// /// The ID of the quest. /// True if the quest is completed, false otherwise. public static bool IsQuestCompleted(int questId) { return PlayerPrefs.GetInt($"{QuestCompletionPrefix}{questId}", 0) == 1; } /// /// Clears the local progress and completion status of daily quests stored in PlayerPrefs. /// public static void ClearLocalDayleQuestProgress() { QuestItem[] quests = GameInstance.Singleton.questData; foreach (var quest in quests) { if (quest.questType == QuestType.Daily) { // Clear progress for the daily quest PlayerPrefs.DeleteKey($"{QuestProgressPrefix}{quest.questId}"); // Clear completion status for the daily quest PlayerPrefs.DeleteKey($"{QuestCompletionPrefix}{quest.questId}"); } } PlayerPrefs.Save(); Debug.Log("Local daily quest progress and completion status cleared."); } /// /// Saves the completion time of a quest locally in PlayerPrefs using a provided server time. /// /// The ID of the quest. /// The completion time of the quest, provided by the server. public static void SaveQuestCompletionTime(int questId, DateTime completionTime) { string key = $"QUEST_COMPLETION_TIME_{questId}"; // Save the provided completion time as a string in the format "yyyy-MM-dd HH:mm:ss" string formattedTime = completionTime.ToString("yyyy-MM-dd HH:mm:ss"); PlayerPrefs.SetString(key, formattedTime); PlayerPrefs.Save(); Debug.Log($"Quest {questId} completion time saved locally: {formattedTime}"); } /// /// Marks a coupon as used by saving its ID in PlayerPrefs and in the backend if initialized. /// /// The unique ID of the coupon. public static void MarkCouponAsUsed(string couponId) { List usedCoupons = GetUsedCoupons(); if (!usedCoupons.Contains(couponId)) { usedCoupons.Add(couponId); SaveUsedCoupons(usedCoupons); if (BackendManager.Singleton.CheckInitialized()) { BackendManager.Singleton.StartCoroutine(SaveUsedCouponCoroutine(couponId)); } } } /// /// Coroutine to save the used coupon via the BackendManager. /// /// The unique ID of the coupon. /// IEnumerator for coroutine execution. private static IEnumerator SaveUsedCouponCoroutine(string couponId) { Task saveTask = BackendManager.Singleton.SaveUsedCouponAsync(couponId); yield return new WaitUntil(() => saveTask.IsCompleted); if (saveTask.Exception != null) { Debug.LogError($"Failed to save used coupon {couponId} via BackendManager: {saveTask.Exception}"); } else { Debug.Log($"Coupon {couponId} marked as used and saved via BackendManager successfully."); } } /// /// Checks if a coupon has already been used. /// /// The unique ID of the coupon. /// True if the coupon has been used, false otherwise. public static bool IsCouponUsed(string couponId) { List usedCoupons = GetUsedCoupons(); return usedCoupons.Contains(couponId); } /// /// Saves the list of used coupons locally using PlayerPrefs. /// /// A list of used coupon IDs. public static void SaveUsedCoupons(List usedCoupons) { string couponsString = string.Join(",", usedCoupons); PlayerPrefs.SetString(UsedCouponsKey, couponsString); PlayerPrefs.Save(); } /// /// Retrieves the list of used coupons from PlayerPrefs. /// /// A list of used coupon IDs. public static List GetUsedCoupons() { string couponsString = PlayerPrefs.GetString(UsedCouponsKey, string.Empty); return new List(couponsString.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)); } /// /// Saves daily rewards data locally in JSON form. /// public static void SetDailyRewardsLocal(DailyRewardsData data) { string json = JsonUtility.ToJson(data); PlayerPrefs.SetString(DailyRewardsKey, json); PlayerPrefs.Save(); } /// /// Retrieves the locally saved daily rewards data. Returns defaults if not found. /// public static DailyRewardsData GetDailyRewardsLocal() { if (!PlayerPrefs.HasKey(DailyRewardsKey)) { return new DailyRewardsData(); } string json = PlayerPrefs.GetString(DailyRewardsKey); DailyRewardsData data = JsonUtility.FromJson(json); return data ?? new DailyRewardsData(); } /// /// Saves new player rewards data locally in JSON form. /// public static void SetNewPlayerRewardsLocal(NewPlayerRewardsData data) { string json = JsonUtility.ToJson(data); PlayerPrefs.SetString(NewPlayerRewardsKey, json); PlayerPrefs.Save(); } /// /// Retrieves the locally saved new player rewards data. Returns defaults if not found. /// public static NewPlayerRewardsData GetNewPlayerRewardsLocal() { if (!PlayerPrefs.HasKey(NewPlayerRewardsKey)) { return new NewPlayerRewardsData(); } string json = PlayerPrefs.GetString(NewPlayerRewardsKey); NewPlayerRewardsData data = JsonUtility.FromJson(json); return data ?? new NewPlayerRewardsData(); } /// /// Saves the next daily reset DateTime as a string. /// public static void SetNextDailyReset(DateTime nextReset) { // Convert to ticks or string long ticks = nextReset.Ticks; PlayerPrefs.SetString(NextDailyResetKey, ticks.ToString()); PlayerPrefs.Save(); } /// /// Retrieves the next daily reset DateTime. /// public static DateTime GetNextDailyReset() { if (!PlayerPrefs.HasKey(NextDailyResetKey)) return DateTime.MinValue; string ticksString = PlayerPrefs.GetString(NextDailyResetKey); if (long.TryParse(ticksString, out long ticks)) { return new DateTime(ticks); } return DateTime.MinValue; } /// /// Simple data structure to hold daily rewards info for local save. /// [Serializable] public class DailyRewardsData { public DateTime firstClaimDate; public List claimedRewards = new List(); } /// /// Simple data structure to hold new player rewards info for local save. /// [Serializable] public class NewPlayerRewardsData { public DateTime accountCreationDate; public List claimedRewards = new List(); } [Serializable] private class UnlockedSkinsData { public List unlockedSkins; } } }