using BulletHellTemplate; using Firebase.Auth; using Firebase.Firestore; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using UnityEngine; using static BulletHellTemplate.PlayerSave; namespace BulletHellTemplate { public class FirebaseSave : MonoBehaviour { public static FirebaseSave Singleton; public FirebaseUser user; public FirebaseFirestore firestore; private void Awake() { if (Singleton == null) { Singleton = this; } else { Destroy(gameObject); return; } DontDestroyOnLoad(gameObject); } public void InitializeFirebase(FirebaseUser _user, FirebaseFirestore _firestore) { user = _user; firestore = _firestore; } /// /// Updates the player's icon in Firestore. /// /// The new icon ID to set. public async Task UpdatePlayerIcon(string newIconId) { if (user != null) { try { DocumentReference docRef = firestore.Collection("Players").Document(user.UserId); await docRef.UpdateAsync("PlayerIcon", newIconId); Debug.Log("Player icon updated successfully."); } catch (Exception e) { Debug.LogError("Failed to update player icon: " + e.Message); } } else { Debug.LogError("No user is logged in."); } } /// /// Updates the player's frame in Firestore. /// /// The new frame ID to set. public async Task UpdatePlayerFrame(string newFrameId) { if (user != null) { try { DocumentReference docRef = firestore.Collection("Players").Document(user.UserId); await docRef.UpdateAsync("PlayerFrame", newFrameId); Debug.Log("Player frame updated successfully."); } catch (Exception e) { Debug.LogError("Failed to update player frame: " + e.Message); } } else { Debug.LogError("No user is logged in."); } } /// /// Updates the player's account level and current experience in Firestore. /// /// The new account level to set. /// The new current experience to set. public async Task UpdatePlayerAccountLevel(int level, int currentExp) { if (user != null) { try { DocumentReference docRef = firestore.Collection("Players").Document(user.UserId); var data = new Dictionary { { "AccountLevel", level }, { "AccountCurrentExp", currentExp } }; await docRef.SetAsync(data, SetOptions.MergeAll); Debug.Log("Account info updated successfully."); } catch (Exception e) { Debug.LogError("Failed to update account info: " + e.Message); } } else { Debug.LogError("No user is logged in."); } } /// /// Updates the player's name in Firestore. /// /// The new player name to set. public async Task UpdatePlayerName(string newName) { if (user != null) { try { // Validate the new player name if (string.IsNullOrWhiteSpace(newName) || newName.Length < 3 || newName.Length > 14) { Debug.LogError("Player name must be between 3 and 14 characters."); return; } // Check if the new player name already exists in the database bool nameExists = await CheckIfNameExists(newName); if (nameExists) { Debug.LogError("The player name already exists."); return; } // Update the player's name in Firestore DocumentReference docRef = firestore.Collection("Players").Document(user.UserId); await docRef.UpdateAsync("PlayerName", newName); // Update the player's name locally PlayerSave.SetPlayerName(newName); Debug.Log("Player name updated successfully."); } catch (Exception e) { Debug.LogError("Failed to update player name: " + e.Message); } } else { Debug.LogError("No user is logged in."); } } /// /// Checks if a player name already exists in the Firestore database. /// /// The name to check. /// True if the name exists; otherwise, false. public async Task CheckIfNameExists(string playerName) { try { // Reference to the "Players" collection CollectionReference playersCollection = firestore.Collection("Players"); // Create a query to find documents where "PlayerName" equals the provided name Query query = playersCollection.WhereEqualTo("PlayerName", playerName); // Execute the query and get a snapshot of the results QuerySnapshot querySnapshot = await query.GetSnapshotAsync(); // Check if any document exists in the result return querySnapshot.Count > 0; } catch (Exception e) { Debug.LogError("Failed to check if name exists: " + e.Message); return false; } } /// /// Updates the amount of a specific currency in Firestore. /// /// The ID of the currency to update. /// The new amount to set for the currency. public async Task UpdateCurrencies(string currencyId, int newAmount) { if (user != null) { try { DocumentReference currencyDocRef = firestore .Collection("Players") .Document(user.UserId) .Collection("Currencies") .Document(currencyId); await currencyDocRef.UpdateAsync("amount", newAmount); Debug.Log($"Currency {currencyId} updated successfully to {newAmount}."); } catch (Exception e) { Debug.LogError($"Failed to update currency {currencyId}: " + e.Message); } } else { Debug.LogError("No user is logged in."); } } /// /// Updates the selected character for the logged-in user in Firestore. /// If the field doesn't exist, it creates it. /// /// The ID of the selected character to save. public async Task UpdateSelectedCharacter(int characterId) { if (user != null) { try { DocumentReference docRef = firestore.Collection("Players").Document(user.UserId); await docRef.UpdateAsync("selectedCharacter", characterId); Debug.Log($"Selected character updated successfully: {characterId}"); } catch (Exception e) { if (e.Message.Contains("No document to update")) { Debug.LogWarning("Document does not exist, creating field instead."); Dictionary data = new Dictionary { { "selectedCharacter", characterId } }; await firestore.Collection("Players").Document(user.UserId).SetAsync(data, SetOptions.MergeAll); Debug.Log($"Selected character created successfully: {characterId}"); } else { Debug.LogError($"Failed to update or create selected character: {e.Message}"); } } } else { Debug.LogError("No user is logged in."); } } /// /// Updates the list of purchased items for the currently logged-in user in Firestore. /// /// A task representing the asynchronous operation. public async Task SavePurchasedItemsAsync() { try { if (user == null) { Debug.LogError("No user is currently logged in."); return; } string userId = user.UserId; var characters = MonetizationManager.Singleton.GetPurchasedCharacters(); var icons = MonetizationManager.Singleton.GetPurchasedIcons(); var frames = MonetizationManager.Singleton.GetPurchasedFrames(); var shopItems = MonetizationManager.Singleton.GetPurchasedShopItems(); // Convert lists to the format suitable for Firestore var characterData = characters.Select(c => JsonUtility.ToJson(c)).ToList(); var iconData = icons.Select(i => JsonUtility.ToJson(i)).ToList(); var frameData = frames.Select(f => JsonUtility.ToJson(f)).ToList(); var shopItemData = shopItems.Select(s => JsonUtility.ToJson(s)).ToList(); var db = FirebaseFirestore.DefaultInstance; var batch = db.StartBatch(); // Save purchased characters var characterDocRef = db.Collection("Players").Document(userId).Collection("PurchasedItems").Document("Characters"); batch.Set(characterDocRef, new { Characters = characterData }); // Save purchased icons var iconDocRef = db.Collection("Players").Document(userId).Collection("PurchasedItems").Document("Icons"); batch.Set(iconDocRef, new { Icons = iconData }); // Save purchased frames var frameDocRef = db.Collection("Players").Document(userId).Collection("PurchasedItems").Document("Frames"); batch.Set(frameDocRef, new { Frames = frameData }); // Save purchased shop items var shopItemDocRef = db.Collection("Players").Document(userId).Collection("PurchasedItems").Document("ShopItems"); batch.Set(shopItemDocRef, new { ShopItems = shopItemData }); // Commit the batch operation await batch.CommitAsync(); Debug.Log("Purchased items saved successfully."); } catch (Exception ex) { Debug.LogError("Error saving purchased items: " + ex.Message); } } /// /// Saves the upgrades of a specific character to Firebase Firestore. /// /// The ID of the character. /// The dictionary containing upgrade data. public async Task SaveCharacterUpgradesAsync(string characterId, Dictionary upgrades) { if (user != null) { try { DocumentReference upgradesDocRef = firestore .Collection("Players") .Document(user.UserId) .Collection("Characters") .Document(characterId) .Collection("Upgrades") .Document("Stats"); Dictionary upgradesData = new Dictionary(); foreach (var upgrade in upgrades) { upgradesData[upgrade.Key.ToString()] = upgrade.Value; } await upgradesDocRef.SetAsync(upgradesData); Debug.Log($"Character upgrades for {characterId} saved successfully."); } catch (Exception e) { Debug.LogError($"Failed to save character upgrades for {characterId}: " + e.Message); } } else { Debug.LogError("No user is logged in."); } } /// /// Saves the unlocked skins for a specific character to Firebase Firestore. /// /// The ID of the character as a string. /// List of unlocked skin indices. public async Task SaveCharacterUnlockedSkinsAsync(string characterId, List unlockedSkins) { if (user!= null) { try { DocumentReference skinsDocRef = firestore .Collection("Players") .Document(user.UserId) .Collection("Characters") .Document(characterId) .Collection("UnlockedSkins") .Document("Skins"); Dictionary skinsData = new Dictionary { { "skins", unlockedSkins } }; await skinsDocRef.SetAsync(skinsData); Debug.Log($"Character unlocked skins for {characterId} saved successfully."); } catch (Exception e) { Debug.LogError($"Failed to save unlocked skins for {characterId}: {e.Message}"); } } else { Debug.LogError("No user is logged in."); } } /// /// Saves the unlocked maps for the player to Firebase Firestore. /// /// A task representing the asynchronous operation. public async Task SaveUnlockedMapsAsync() { if (user == null) { Debug.LogError("No user is logged in."); return; } try { List unlockedMapIds = PlayerSave.GetUnlockedMaps(); Dictionary mapsData = new Dictionary(); foreach (int mapId in unlockedMapIds) { mapsData[mapId.ToString()] = true; } DocumentReference mapsDocRef = firestore .Collection("Players") .Document(user.UserId) .Collection("Progress") .Document("UnlockedMaps"); await mapsDocRef.SetAsync(mapsData); Debug.Log("Unlocked maps saved to Firebase successfully."); } catch (Exception e) { Debug.LogError("Failed to save unlocked maps: " + e.Message); } } /// /// Saves a used coupon to Firebase Firestore. /// /// The unique ID of the coupon. /// A task representing the asynchronous operation. public async Task SaveUsedCouponAsync(string couponId) { if (user == null) { Debug.LogError("No user is logged in."); return; } try { DocumentReference couponsDocRef = firestore .Collection("Players") .Document(user.UserId) .Collection("Progress") .Document("UsedCoupons"); DocumentSnapshot snapshot = await couponsDocRef.GetSnapshotAsync(); if (!snapshot.Exists) { // If the document does not exist, create it with the initial coupon await couponsDocRef.SetAsync(new Dictionary { { couponId, true } }); Debug.Log($"Coupon {couponId} created and saved to Firebase successfully."); } else { await couponsDocRef.UpdateAsync(new Dictionary { { couponId, true } }); Debug.Log($"Coupon {couponId} saved to Firebase successfully."); } } catch (Exception e) { Debug.LogError($"Failed to save used coupon {couponId} to Firebase: " + e.Message); } } /// /// Saves the progress of a quest to Firebase Firestore. /// /// The ID of the quest. /// The progress value to save. /// A task representing the asynchronous operation. public async Task SaveQuestProgressAsync(int questId, int progress) { if (user == null) { Debug.LogError("No user is logged in."); return; } try { DocumentReference questsDocRef = firestore .Collection("Players") .Document(user.UserId) .Collection("Progress") .Document("Quests"); DocumentSnapshot snapshot = await questsDocRef.GetSnapshotAsync(); if (!snapshot.Exists) { // If the document does not exist, create it with the initial quest progress await questsDocRef.SetAsync(new Dictionary { { questId.ToString(), progress } }); Debug.Log($"Quest progress for {questId} created and saved successfully in Firebase."); } else { // If the document exists, update the quest progress await questsDocRef.UpdateAsync(new Dictionary { { questId.ToString(), progress } }); Debug.Log($"Quest progress for {questId} updated successfully in Firebase."); } } catch (Exception e) { Debug.LogError($"Failed to save quest progress for {questId}: {e.Message}"); } } /// /// Saves the completion status of a quest to Firebase Firestore, including the completion timestamp. /// /// The ID of the quest. /// A task representing the asynchronous operation. public async Task SaveQuestCompletionAsync(int questId) { if (user == null) { Debug.LogError("No user is logged in."); return; } try { DocumentReference questsDocRef = firestore .Collection("Players") .Document(user.UserId) .Collection("Progress") .Document("Quests"); string questKey = "Complete " + questId.ToString(); // Format key as "Complete X" // Get the current timestamp Timestamp completionTimestamp = Timestamp.GetCurrentTimestamp(); // Check if the document exists DocumentSnapshot snapshot = await questsDocRef.GetSnapshotAsync(); if (snapshot.Exists) { await questsDocRef.UpdateAsync(new Dictionary { { questKey, 1 }, // Assuming 1 represents completion { questKey + "_Timestamp", completionTimestamp } // Save completion timestamp }); } else { // If the document doesn't exist, create it with the quest completion status and timestamp await questsDocRef.SetAsync(new Dictionary { { questKey, 1 }, // Assuming 1 represents completion { questKey + "_Timestamp", completionTimestamp } // Save completion timestamp }); } Debug.Log($"Quest completion for {questId} saved successfully in Firebase with timestamp."); } catch (Exception e) { Debug.LogError($"Failed to save quest completion for {questId}: {e.Message}"); } } /// /// Saves the number of monsters killed by the player to their Firestore document. /// The total number of kills is incremented by the amount achieved in the current game session. /// /// The number of monsters killed in the current session. public async Task SaveMonstersKilledAsync(int monstersKilled) { if (user == null) { Debug.LogError("No user is logged in."); return; } DocumentReference playerDocRef = firestore .Collection("Players") .Document(user.UserId); try { DocumentSnapshot snapshot = await playerDocRef.GetSnapshotAsync(); double currentScore = 0; // If the document exists and the "score" field is present, retrieve the current score. if (snapshot.Exists && snapshot.ContainsField("score")) { currentScore = Convert.ToDouble(snapshot.GetValue("score")); } else { Debug.Log("No score found for the player, starting from 0."); } // Add the new kills to the current score currentScore += monstersKilled; // Update the Firestore document with the new score await playerDocRef.UpdateAsync("score", currentScore.ToString()); Debug.Log($"Monsters killed ({monstersKilled}) added to player's score. New score: {currentScore}"); } catch (Exception e) { Debug.LogError($"Failed to save monsters killed to Firestore: {e.Message}"); } } /// /// Saves the PlayerCharacterFavourite to Firestore. /// /// The ID of the character to save as favourite. public async Task SavePlayerCharacterFavouriteAsync(int characterId) { if (user != null) { try { DocumentReference docRef = firestore.Collection("Players").Document(user.UserId); DocumentSnapshot snapshot = await docRef.GetSnapshotAsync(); if (snapshot.Exists) { Dictionary playerData = snapshot.ToDictionary(); if (playerData.ContainsKey("PlayerCharacterFavourite")) { // Update the existing field await docRef.UpdateAsync("PlayerCharacterFavourite", characterId); } else { // Create the field if it doesn't exist await docRef.SetAsync(new Dictionary { { "PlayerCharacterFavourite", characterId } }, SetOptions.MergeFields("PlayerCharacterFavourite")); } Debug.Log("PlayerCharacterFavourite saved successfully."); } else { Debug.LogWarning("Player document does not exist."); } } catch (Exception e) { Debug.LogError("Failed to save PlayerCharacterFavourite: " + e.Message); } } else { Debug.LogError("No user is logged in."); } } public async Task SaveClaimedRewardAsync(string passItemId) { if (user == null) { Debug.LogError("No user is logged in."); return; } try { DocumentReference rewardsDocRef = firestore .Collection("Players") .Document(user.UserId) .Collection("Progress") .Document("BattlePassRewards"); DocumentSnapshot snapshot = await rewardsDocRef.GetSnapshotAsync(); if (snapshot.Exists) { await rewardsDocRef.UpdateAsync(new Dictionary { { passItemId, true } }); } else { await rewardsDocRef.SetAsync(new Dictionary { { passItemId, true } }); } Debug.Log($"Claimed reward {passItemId} saved to Firebase."); } catch (Exception e) { Debug.LogError($"Failed to save claimed reward {passItemId} to Firebase: {e.Message}"); } } public async Task SaveBattlePassProgressAsync(int currentXP, int currentLevel, bool isUnlocked) { if (user == null) { Debug.LogError("No user is logged in."); return; } try { DocumentReference battlePassDocRef = firestore .Collection("Players") .Document(user.UserId) .Collection("Progress") .Document("BattlePass"); Dictionary battlePassData = new Dictionary { { "CurrentXP", currentXP }, { "CurrentLevel", currentLevel }, { "IsUnlocked", isUnlocked } }; await battlePassDocRef.SetAsync(battlePassData); Debug.Log("Battle Pass progress saved to Firebase successfully."); } catch (Exception e) { Debug.LogError("Failed to save Battle Pass progress: " + e.Message); } } /// /// Saves basic character info (skin, level, experience, mastery) to Firestore and updates local PlayerSave. /// /// ID of the character. /// Index of the selected skin. Use -1 if no skin is selected. /// Character level. /// Current experience points. /// Current mastery level. /// Current mastery experience points. public async Task SaveCharacterBasicInfoAsync(int characterId, int skinIndex, int level, int currentExp, int masteryLevel, int masteryExp) { try { DocumentReference charDocRef = firestore .Collection("Players") .Document(user.UserId) .Collection("Characters") .Document(characterId.ToString()); Dictionary data = new Dictionary { { "CharacterSelectedSkin", skinIndex }, { "CharacterLevel", level }, { "CharacterCurrentExp", currentExp }, { "CharacterMasteryLevel", masteryLevel }, { "CharacterCurrentMasteryExp", masteryExp } }; // Update Firestore await charDocRef.SetAsync(data, SetOptions.MergeAll); Debug.Log($"Character basic info saved for CharacterId {characterId}."); ; } catch (Exception e) { Debug.LogError($"Failed to save character basic info for {characterId}: {e.Message}"); } } /// /// Saves an item to the sub-collection named by 'slotName' under the character document. /// Each item is stored in its own document, named by the unique item identifier. /// Path: Players/{userId}/Characters/{characterId}/{slotName}/{uniqueItemGuid} /// /// ID of the character. /// Slot name (e.g. "WeaponSlot" or "RuneSlot"). /// Unique ID for this item doc. /// ID of the item (scriptable or otherwise). /// Level of the item. public async Task SaveCharacterItemAsync(int characterId, string slotName, string uniqueItemGuid, string itemId, int itemLevel) { try { // Example: /Players/{userId}/Characters/{characterId}/{slotName}/{uniqueItemGuid} DocumentReference itemDocRef = firestore .Collection("Players") .Document(user.UserId) .Collection("Characters") .Document(characterId.ToString()) .Collection(slotName) // <-- The "slotName" subcollection .Document(uniqueItemGuid); Dictionary data = new Dictionary { { "ItemId", itemId }, { "ItemLevel", itemLevel } }; await itemDocRef.SetAsync(data, SetOptions.MergeAll); Debug.Log($"[SaveCharacterItemAsync] Item saved: Char {characterId}, Slot {slotName}, Doc {uniqueItemGuid}."); } catch (Exception e) { Debug.LogError($"Failed to save item for CharId {characterId}, slot {slotName}: {e.Message}"); } } /// /// Removes (unequips) an item document from the specified slot of the character. /// Path: Players/{userId}/Characters/{characterId}/{slotName}/{uniqueItemGuid} /// public async Task RemoveCharacterItemAsync(int characterId, string slotName, string uniqueItemGuid) { try { DocumentReference docRef = firestore .Collection("Players") .Document(user.UserId) .Collection("Characters") .Document(characterId.ToString()) .Collection(slotName) .Document(uniqueItemGuid); await docRef.DeleteAsync(); Debug.Log($"[RemoveCharacterItemAsync] Removed item {uniqueItemGuid} from slot {slotName} of Char {characterId}."); } catch (Exception e) { Debug.LogError($"Failed to remove item {uniqueItemGuid} from char {characterId}, slot {slotName}: {e.Message}"); } } /// /// Saves a purchased inventory item into Firestore (Players/{userId}/PurchasedItems/Items/{uniqueItemGuid}). /// Also updates local data if desired. /// public async Task SavePurchasedInventoryItemAsync(PurchasedInventoryItem purchasedItem) { if (user == null) { Debug.LogError("No user is logged in."); return; } try { DocumentReference itemDocRef = firestore .Collection("Players") .Document(user.UserId) .Collection("PurchasedItems") .Document("Items") .Collection("List") .Document(purchasedItem.uniqueItemGuid); Dictionary data = new Dictionary { { "uniqueItemGuid", purchasedItem.uniqueItemGuid }, { "itemId", purchasedItem.itemId }, { "itemLevel", purchasedItem.itemLevel } }; Dictionary upgradesDict = new Dictionary(); if (purchasedItem.upgrades != null) { foreach (var kvp in purchasedItem.upgrades) { upgradesDict[kvp.Key.ToString()] = kvp.Value; } } data["itemUpgrades"] = upgradesDict; await itemDocRef.SetAsync(data, SetOptions.MergeAll); Debug.Log($"Purchased item saved: {purchasedItem.uniqueItemGuid} -> {purchasedItem.itemId}."); } catch (Exception e) { Debug.LogError($"Failed to save purchased inventory item {purchasedItem.uniqueItemGuid}: {e.Message}"); } } /// /// Deletes a purchased inventory item from Firestore: /// Players/{userId}/PurchasedItems/Items/List/{uniqueItemGuid}. /// Also checks all characters to remove the item from sub-collection Items if present. /// Finally, removes it locally from MonetizationManager and InventorySave. /// /// The unique item GUID to delete. public async Task DeletePurchasedInventoryItemAsync(string uniqueItemGuid) { if (user == null) { Debug.LogError("No user is logged in."); return; } try { // 1) Remove from "PurchasedItems" DocumentReference itemDocRef = firestore .Collection("Players") .Document(user.UserId) .Collection("PurchasedItems") .Document("Items") .Collection("List") .Document(uniqueItemGuid); await itemDocRef.DeleteAsync(); Debug.Log($"Item {uniqueItemGuid} deleted from PurchasedItems."); // 2) Check all characters for this item foreach (var cd in GameInstance.Singleton.characterData) { int charId = cd.characterId; // Attempt to remove from the character's "Items" sub-collection DocumentReference charItemDoc = firestore .Collection("Players") .Document(user.UserId) .Collection("Characters") .Document(charId.ToString()) .Collection("Items") .Document(uniqueItemGuid); await charItemDoc.DeleteAsync(); // It's okay if doc doesn't exist, no error is thrown for a missing doc } // 3) Remove locally from MonetizationManager var allItems = MonetizationManager.Singleton.GetPurchasedInventoryItems(); var foundItem = allItems.Find(i => i.uniqueItemGuid == uniqueItemGuid); if (foundItem != null) { allItems.Remove(foundItem); // Update the manager MonetizationManager.Singleton.UpdatePurchasedItems( MonetizationManager.Singleton.GetPurchasedCharacters(), MonetizationManager.Singleton.GetPurchasedIcons(), MonetizationManager.Singleton.GetPurchasedFrames(), MonetizationManager.Singleton.GetPurchasedShopItems(), allItems ); } Debug.Log($"Item {uniqueItemGuid} fully deleted locally and online."); } catch (Exception e) { Debug.LogError($"Failed to delete item {uniqueItemGuid}: {e.Message}"); } } /// /// Saves daily rewards data to Firebase. /// public async Task SaveDailyRewardsDataAsync(DailyRewardsData dailyData) { try { if (user == null) return; DocumentReference docRef = firestore .Collection("Players") .Document(user.UserId) .Collection("DailyRewards") .Document("RewardData"); Dictionary saveData = new Dictionary { { "firstClaimDate", Timestamp.FromDateTime(dailyData.firstClaimDate.ToUniversalTime()) }, { "claimedRewards", dailyData.claimedRewards } }; await docRef.SetAsync(saveData); } catch (Exception e) { Debug.LogError($"Failed to save daily rewards data: {e.Message}"); } } /// /// Saves new player rewards data to Firebase. /// public async Task SaveNewPlayerRewardsDataAsync(NewPlayerRewardsData newPlayerData) { try { if (user == null) return; DocumentReference docRef = firestore .Collection("Players") .Document(user.UserId) .Collection("NewPlayerRewards") .Document("RewardData"); Dictionary saveData = new Dictionary { { "accountCreationDate", Timestamp.FromDateTime(newPlayerData.accountCreationDate.ToUniversalTime()) }, { "claimedRewards", newPlayerData.claimedRewards } }; await docRef.SetAsync(saveData); } catch (Exception e) { Debug.LogError($"Failed to save new player rewards data: {e.Message}"); } } } }