using System.Collections.Generic; using System.Linq; using UnityEngine; namespace BulletHellTemplate { /// /// Manages the display of quests in the UI. /// Displays available quests, handles quest completion logic, /// and can optionally hide completed quests. /// public class UIQuestMenu : MonoBehaviour { #region Singleton and Public Fields [Tooltip("Prefab for individual quest entries")] public QuestEntry questEntryPrefab; [Tooltip("Container where quest entries will be instantiated")] public Transform container; /// /// Singleton reference for global access to UIQuestMenu. /// public static UIQuestMenu Singleton; [Tooltip("Whether or not to hide completed quests from the UI")] public bool hideCompletedQuests; /// /// Current language code or identifier used by the UI. /// [HideInInspector] public string currentLang; #endregion #region Unity Callbacks /// /// Initializes the singleton reference on startup. /// private void Start() { Singleton = this; } /// /// Called when the object becomes enabled and active. /// Sets the current language and loads quests into the UI. /// private void OnEnable() { currentLang = LanguageManager.LanguageManager.Instance.GetCurrentLanguage(); LoadQuests(); } #endregion #region Public Methods /// /// Loads and displays the available quests in the UI. /// If 'hideCompletedQuests' is true, completed quests (except repeat) will be skipped. /// Otherwise, all quests are shown, with an indicator for those that are completed. /// Additionally, it checks if the player's level or characters' levels /// exceed any quest requirements to set progress to 100%. /// public void LoadQuests() { // Clear existing children in the container to avoid duplication foreach (Transform child in container) { Destroy(child.gameObject); } // Retrieve all available quests from the game instance List allQuests = GameInstance.Singleton.questData.ToList(); // Adjust progress for level-based quests (but don't auto-complete) SetLevelBasedQuestsToMaxProgress(allQuests); // Display each quest foreach (QuestItem quest in allQuests) { // If quest is not repeat and is completed, skip if hideCompletedQuests is true bool isCompleted = PlayerSave.IsQuestCompleted(quest.questId); if (hideCompletedQuests && quest.questType != QuestType.Repeat && isCompleted) { continue; } // Instantiate a new QuestEntry QuestEntry newQuestEntry = Instantiate(questEntryPrefab, container); // Translate title and description string questTitleTranslated = GetTranslatedString(quest.titleTranslated, quest.title, currentLang); string questDescriptionTranslated = GetTranslatedString(quest.descriptionTranslated, quest.description, currentLang); // Set up the QuestEntry newQuestEntry.Setup( questTitleTranslated, questDescriptionTranslated, quest, isCompleted ); // Load current progress from player data int currentProgress = PlayerSave.LoadQuestProgress(quest.questId); // Retrieve target amount from quest requirements int targetAmount = quest.requirement.targetAmount; // Update the progress bar and button interactability newQuestEntry.UpdateProgress(currentProgress, targetAmount); } } /// /// Called by the QuestEntry when the player tries to complete the quest. /// Checks if the quest is indeed completable, then applies rewards. /// Marks the quest as completed or resets its progress if it's a repeat quest. /// Finally, reloads the quest UI for updated feedback. /// /// The quest item the player is attempting to complete. public void OnUserAttemptCompleteQuest(QuestItem quest) { if (quest == null) return; // Check if already completed (or if it's repeat, we don't care, we keep resetting) bool isQuestCompleted = PlayerSave.IsQuestCompleted(quest.questId); if (isQuestCompleted && quest.questType != QuestType.Repeat) { Debug.Log("Quest is already completed."); return; } // Verify if progress is sufficient int currentProgress = PlayerSave.LoadQuestProgress(quest.questId); int targetAmount = quest.requirement.targetAmount; if (currentProgress < targetAmount) { Debug.Log($"Quest '{quest.title}' is not yet complete. Current progress: {currentProgress}/{targetAmount}."); return; } // Progress is enough to conclude ApplyQuestReward(quest); // If it's a repeat quest, reset progress to 0 if (quest.questType == QuestType.Repeat) { PlayerSave.SaveQuestProgress(quest.questId, 0); } else { // Mark quest as permanently completed PlayerSave.SaveQuestCompletion(quest.questId); } // Reload the UI to show updated quest states LoadQuests(); } /// /// Applies the quest's direct EXP and currency to the player, /// then unlocks items via the BattlePass system if necessary. /// This includes: AccountExp, BattlePassExp, and CharacterExp /// for the currently selected character if 'selectedCharacterExp' > 0. /// /// The quest to apply rewards from. public void ApplyQuestReward(QuestItem questItem) { if (questItem == null) return; // Add account EXP (if > 0) if (questItem.accountExp > 0) { GameManager.Singleton.AddAccountExp(questItem.accountExp); } // Add battle pass EXP (if > 0) if (questItem.battlePassExp > 0) { GameManager.Singleton.AddBattlePassExp(questItem.battlePassExp); } // Add character EXP to the currently selected character (if > 0) if (questItem.selectedCharacterExp > 0) { int selectedCharId = PlayerSave.GetSelectedCharacter(); GameManager.Singleton.AddCharacterExp(selectedCharId, questItem.selectedCharacterExp); GameManager.Singleton.SaveCharacterBasicInfo(selectedCharId); } // Add currency if (questItem.currencyAmount > 0 && !string.IsNullOrEmpty(questItem.currencyReward)) { int currentCurrency = MonetizationManager.GetCurrency(questItem.currencyReward); currentCurrency += questItem.currencyAmount; MonetizationManager.SetCurrency(questItem.currencyReward, currentCurrency); } // Unlock item reward if any (CharacterReward, IconReward, FrameReward, or ItemReward) if (questItem.questReward != QuestReward.None) { BattlePassItem tempBattlePassItem = ScriptableObject.CreateInstance(); // Basic info tempBattlePassItem.passId = "Quest_" + questItem.questId.ToString(); tempBattlePassItem.itemTitle = questItem.title; tempBattlePassItem.itemIcon = questItem.icon; tempBattlePassItem.rewardTier = BattlePassItem.RewardTier.Free; // or Paid, if desired switch (questItem.questReward) { case QuestReward.CharacterReward: tempBattlePassItem.rewardType = BattlePassItem.RewardType.CharacterReward; tempBattlePassItem.characterData = new CharacterData[] { questItem.characterData }; break; case QuestReward.IconReward: tempBattlePassItem.rewardType = BattlePassItem.RewardType.IconReward; tempBattlePassItem.iconReward = questItem.iconItem; break; case QuestReward.FrameReward: tempBattlePassItem.rewardType = BattlePassItem.RewardType.FrameReward; tempBattlePassItem.frameReward = questItem.frameItem; break; case QuestReward.ItemReward: tempBattlePassItem.rewardType = BattlePassItem.RewardType.InventoryItemReward; tempBattlePassItem.inventoryItems = new InventoryItem[] { questItem.inventoryItem }; break; } // Unlock via MonetizationManager MonetizationManager.Singleton.BattlePassUnlockItem(tempBattlePassItem); } } /// /// Sets any level-based quests' progress to 100% if the player or their characters /// already meet or exceed the required level, but does not complete them automatically. /// /// List of all available quests. public void SetLevelBasedQuestsToMaxProgress(List allQuests) { if (allQuests == null) return; int accountLevel = PlayerSave.GetAccountLevel(); // Create a lookup for character levels Dictionary characterLevels = new Dictionary(); foreach (var charData in GameInstance.Singleton.characterData) { int level = PlayerSave.GetCharacterLevel(charData.characterId); characterLevels[charData.characterId] = level; } foreach (var quest in allQuests) { if (quest == null) continue; // If it's completed (and not repeat) skip if (PlayerSave.IsQuestCompleted(quest.questId) && quest.questType != QuestType.Repeat) continue; QuestRequirementType reqType = quest.requirement.requirementType; // We only care about LevelUpAccount or LevelUpCharacter if (reqType == QuestRequirementType.LevelUpAccount) { // If player account level >= required target if (accountLevel >= quest.requirement.targetAmount) { // Just set progress to the target amount (not completing automatically) PlayerSave.SaveQuestProgress(quest.questId, quest.requirement.targetAmount); } } else if (reqType == QuestRequirementType.LevelUpCharacter) { if (quest.requirement.targetCharacter != null) { int targetCharId = quest.requirement.targetCharacter.characterId; if (characterLevels.ContainsKey(targetCharId)) { int currentCharLevel = characterLevels[targetCharId]; if (currentCharLevel >= quest.requirement.targetAmount) { // Set progress to target PlayerSave.SaveQuestProgress(quest.questId, quest.requirement.targetAmount); } } } } } } /// /// Returns a translated string according to the current language /// or a fallback string if no translation is found. /// /// An array of translations for different languages. /// The default (fallback) string if no matching translation is found. /// The language code or identifier currently in use. /// A string in the user's selected language, or the fallback if none is found. public string GetTranslatedString(NameTranslatedByLanguage[] translations, string fallback, string currentLang) { if (translations != null) { foreach (var trans in translations) { if (!string.IsNullOrEmpty(trans.LanguageId) && trans.LanguageId.Equals(currentLang) && !string.IsNullOrEmpty(trans.Translate)) { return trans.Translate; } } } return fallback; } /// /// Returns a translated description string according to the current language /// or a fallback string if no translation is found. /// /// An array of description translations for different languages. /// The default (fallback) string if no matching translation is found. /// The language code or identifier currently in use. /// A string in the user's selected language, or the fallback if none is found. public string GetTranslatedString(DescriptionTranslatedByLanguage[] translations, string fallback, string currentLang) { if (translations != null) { foreach (var trans in translations) { if (!string.IsNullOrEmpty(trans.LanguageId) && trans.LanguageId.Equals(currentLang) && !string.IsNullOrEmpty(trans.Translate)) { return trans.Translate; } } } return fallback; } #endregion } }