352 lines
15 KiB
C#
352 lines
15 KiB
C#
|
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using UnityEngine;
|
||
|
|
||
|
namespace BulletHellTemplate
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Manages the display of quests in the UI.
|
||
|
/// Displays available quests, handles quest completion logic,
|
||
|
/// and can optionally hide completed quests.
|
||
|
/// </summary>
|
||
|
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;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Singleton reference for global access to UIQuestMenu.
|
||
|
/// </summary>
|
||
|
public static UIQuestMenu Singleton;
|
||
|
|
||
|
[Tooltip("Whether or not to hide completed quests from the UI")]
|
||
|
public bool hideCompletedQuests;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Current language code or identifier used by the UI.
|
||
|
/// </summary>
|
||
|
[HideInInspector]
|
||
|
public string currentLang;
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Unity Callbacks
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initializes the singleton reference on startup.
|
||
|
/// </summary>
|
||
|
private void Start()
|
||
|
{
|
||
|
Singleton = this;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Called when the object becomes enabled and active.
|
||
|
/// Sets the current language and loads quests into the UI.
|
||
|
/// </summary>
|
||
|
private void OnEnable()
|
||
|
{
|
||
|
currentLang = LanguageManager.LanguageManager.Instance.GetCurrentLanguage();
|
||
|
LoadQuests();
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Public Methods
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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%.
|
||
|
/// </summary>
|
||
|
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<QuestItem> 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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
/// <param name="quest">The quest item the player is attempting to complete.</param>
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
/// <param name="questItem">The quest to apply rewards from.</param>
|
||
|
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<BattlePassItem>();
|
||
|
|
||
|
// 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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
/// <param name="allQuests">List of all available quests.</param>
|
||
|
public void SetLevelBasedQuestsToMaxProgress(List<QuestItem> allQuests)
|
||
|
{
|
||
|
if (allQuests == null) return;
|
||
|
|
||
|
int accountLevel = PlayerSave.GetAccountLevel();
|
||
|
|
||
|
// Create a lookup for character levels
|
||
|
Dictionary<int, int> characterLevels = new Dictionary<int, int>();
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a translated string according to the current language
|
||
|
/// or a fallback string if no translation is found.
|
||
|
/// </summary>
|
||
|
/// <param name="translations">An array of translations for different languages.</param>
|
||
|
/// <param name="fallback">The default (fallback) string if no matching translation is found.</param>
|
||
|
/// <param name="currentLang">The language code or identifier currently in use.</param>
|
||
|
/// <returns>A string in the user's selected language, or the fallback if none is found.</returns>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a translated description string according to the current language
|
||
|
/// or a fallback string if no translation is found.
|
||
|
/// </summary>
|
||
|
/// <param name="translations">An array of description translations for different languages.</param>
|
||
|
/// <param name="fallback">The default (fallback) string if no matching translation is found.</param>
|
||
|
/// <param name="currentLang">The language code or identifier currently in use.</param>
|
||
|
/// <returns>A string in the user's selected language, or the fallback if none is found.</returns>
|
||
|
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
|
||
|
}
|
||
|
}
|