using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.TextCore.Text; using UnityEngine.UI; namespace BulletHellTemplate { /// /// Handles the UI menu for character selection, filtering, sorting, and detailed character view. /// Applies the stats from character base, level upgrades, and equipped items (including upgrade bonuses). /// public class UICharacterMenu : MonoBehaviour { [Header("Menu Sections")] [Tooltip("Panel that displays the list of characters.")] public GameObject characterSelection; [Tooltip("Panel that displays the details of a specific character.")] public GameObject characterDetails; [Header("Prefabs and Container")] [Tooltip("Prefab for each character entry in the UI.")] public CharacterEntry characterEntryPrefab; [Tooltip("Container to hold the temporarily spawned character model.")] public Transform tempCharacterContainer; [Tooltip("Container to hold the character entries.")] public Transform container; [Tooltip("UI element for displaying skill information.")] public UISkillInfo uiSkillInfo; [Tooltip("Prefab for each skill info entry in the UI.")] public SkillInfoEntry skillInfoPrefab; [Tooltip("Button component for marking a character as favourite.")] public FavouriteCharacter favouriteCharacterButton; [Tooltip("Image component to indicate which character is marked as favourite.")] public Image favouriteSelected; [Tooltip("Container to hold the displayed character skills.")] public Transform containerSkills; [Tooltip("Prefab for skin entry UI.")] public SkinEntry skinEntry; [Tooltip("Container to hold the skin entries.")] public Transform skinsContainer; [Tooltip("UI GameObject for skin selection.")] public GameObject UISkins; [Tooltip("Button to open the skin selection UI.")] public Button openSkinSelection; public Button unlockSkinButton; public TextMeshProUGUI unlockSkinPrice; public Image unlockedCurrencyIcon; public RawImage tempCharacterRendererImage; [Header("UIItemsApply")] [Tooltip("Reference to the UIItemsApply system that manages equipping items.")] public UIItemsApply uiItemsApply; [Header("Upgrade Prefabs and Container")] [Tooltip("Prefab for each upgrade entry in the UI.")] public UpgradeEntry upgradeEntryPrefab; [Tooltip("Container to hold the upgrade entries.")] public Transform containerUpgrades; [Header("Filters")] [Tooltip("Shows only characters that have an active quest requirement.")] public Toggle filterCharacterWithQuest; [Tooltip("Shows only characters that can level up (EXP + cost requirements).")] public Toggle filterCharacterCanUpgrade; [Tooltip("Filters characters by a substring in their names.")] public TMP_InputField searchByWord; [Header("Orders options")] [Tooltip("Sort characters by rarity (Legendary > Epic > Rare > Uncommon > Common). Only one order can be active at a time.")] public Toggle orderByRarity; [Tooltip("Sort characters by their current level (higher first). Only one order can be active at a time.")] public Toggle orderByLevel; [Tooltip("Sort characters by their current mastery level (higher first). Only one order can be active at a time.")] public Toggle orderByMastery; [Header("Order Direction")] [Tooltip("If active, inverts the sorted list to ascending order.")] public Toggle orderByDescending; [Header("UI")] [Tooltip("Button to go back to the character selection list.")] public Button backToCharacterSelection; [Tooltip("Button to confirm the selection of the currently viewed character.")] public Button selectCharacterButton; [Tooltip("Button to level up the currently viewed character.")] public Button levelUpCharacter; [Tooltip("Text that displays the price required to level up.")] public TextMeshProUGUI levelUpPrice; [Tooltip("Icon for the currency used in leveling up.")] public Image currencyLevelUpIcon; [Tooltip("Text that displays the character's name in the details panel.")] public TextMeshProUGUI characterName; [Tooltip("Text that displays the character's description in the details panel.")] public TextMeshProUGUI characterDescription; [Tooltip("Text that displays the character's rarity in the details panel.")] public TextMeshProUGUI characterRarity; [Tooltip("Text that displays the character's class type.")] public TextMeshProUGUI characterClass; [Tooltip("Icon that displays the character's class.")] public Image characterClassIcon; [Tooltip("Icon that displays the character's elemental type.")] public Image characterElementalIcon; [Tooltip("Text that displays the character's current level.")] public TextMeshProUGUI characterLevel; [Tooltip("Text that displays the character's current EXP in the format current/required.")] public TextMeshProUGUI characterCurrentExp; [Tooltip("Image progress bar for character level EXP.")] public Image curentProgressLevelExpBar; [Tooltip("Text that displays the current mastery name.")] public TextMeshProUGUI masteryName; [Tooltip("Icon that displays the current mastery icon.")] public Image masteryIcon; [Tooltip("Text that displays the character's current mastery EXP in the format current/required.")] public TextMeshProUGUI characterCurrentMasteryExp; [Tooltip("Image progress bar for mastery EXP.")] public Image curentProgressMasteryExpBar; [Header("Character Stats (Basic)")] [Tooltip("Displays the base HP of the character.")] public TextMeshProUGUI characterHp; [Tooltip("Displays additional HP bonus (only from buffs/items/runes).")] public TextMeshProUGUI characterHpBonus; [Tooltip("Displays the base damage of the character.")] public TextMeshProUGUI characterDamage; [Tooltip("Displays additional damage bonus (only from buffs/items/runes).")] public TextMeshProUGUI characterDamageBonus; [Tooltip("Displays the base energy/MP of the character.")] public TextMeshProUGUI characterEnergy; [Tooltip("Displays additional energy bonus (only from buffs/items/runes).")] public TextMeshProUGUI characterEnergyBonus; [Header("Rarity Settings")] [Tooltip("The Image that will display the rarity banner sprite.")] public Image rarityImage; [Tooltip("Banner for 'Common' rarity.")] public Sprite commonBanner; [Tooltip("Banner for 'Uncommon' rarity.")] public Sprite uncommonBanner; [Tooltip("Banner for 'Rare' rarity.")] public Sprite rareBanner; [Tooltip("Banner for 'Epic' rarity.")] public Sprite epicBanner; [Tooltip("Banner for 'Legendary' rarity.")] public Sprite legendaryBanner; [Header("Rarity Text Gradient")] [Tooltip("Gradient colors used for 'Common' rarity.")] public TextRarityGradient commonTextColor; [Tooltip("Gradient colors used for 'Uncommon' rarity.")] public TextRarityGradient uncommonTextColor; [Tooltip("Gradient colors used for 'Rare' rarity.")] public TextRarityGradient rareTextColor; [Tooltip("Gradient colors used for 'Epic' rarity.")] public TextRarityGradient epicTextColor; [Tooltip("Gradient colors used for 'Legendary' rarity.")] public TextRarityGradient legendaryTextColor; [Header("Character Detailed Stats UI")] [Tooltip("Displays the character's HP.")] public TextMeshProUGUI hpText; [Tooltip("Displays the character's HP regeneration.")] public TextMeshProUGUI hpRegenText; [Tooltip("Displays the character's HP leech percentage.")] public TextMeshProUGUI hpLeechText; [Tooltip("Displays the character's MP.")] public TextMeshProUGUI mpText; [Tooltip("Displays the character's MP regeneration.")] public TextMeshProUGUI mpRegenText; [Tooltip("Displays the character's base damage.")] public TextMeshProUGUI damageText; [Tooltip("Displays the character's attack speed.")] public TextMeshProUGUI attackSpeedText; [Tooltip("Displays the character's cooldown reduction percentage.")] public TextMeshProUGUI cooldownReductionText; [Tooltip("Displays the character's critical rate percentage.")] public TextMeshProUGUI criticalRateText; [Tooltip("Displays the character's critical damage multiplier.")] public TextMeshProUGUI criticalDamageMultiplierText; [Tooltip("Displays the character's defense value.")] public TextMeshProUGUI defenseText; [Tooltip("Displays the character's shield value.")] public TextMeshProUGUI shieldText; [Tooltip("Displays the character's move speed.")] public TextMeshProUGUI moveSpeedText; [Tooltip("Displays the character's collect range.")] public TextMeshProUGUI collectRangeText; [Header("Stat Colors")] [Tooltip("Color for stat name text.")] public Color statNameColor; [Tooltip("Color for stat value text.")] public Color statValueColor; [Tooltip("Color for stat bonus text.")] public Color statBonusColor; [Header("Error Message Text")] [Tooltip("Displays any relevant error messages during character upgrades.")] public TextMeshProUGUI errorMessageUpgrade; [Header("UI Translations")] [Tooltip("Translations for stat names and common messages.")] public CharacterMenuTranslations uiTranslations; [Header("Events")] [Tooltip("Event invoked when the menu is opened.")] public UnityEvent OnOpenMenu; [Tooltip("Event invoked when the menu is closed.")] public UnityEvent OnCloseMenu; [Tooltip("Event invoked when character details panel opens.")] public UnityEvent OnOpenCharacterDetails; [Tooltip("Event invoked when a character upgrade happens.")] public UnityEvent OnCharacterUpgrade; [Tooltip("Event invoked when an item is added to a character.")] public UnityEvent OnCharacterAddItem; [Tooltip("Event invoked when a rune is added to a character.")] public UnityEvent OnCharacterAddRune; public static UICharacterMenu Singleton { get; private set; } // Holds the currently viewed character ID in the details panel private int currentDetailCharacterId; private int currentDetailCharacterSkin; [HideInInspector]public CharacterModel tempCharacterModel; private string currentLang; private List unlockedCharacters = new List(); private List characterEntries = new List(); private void Start() { Singleton = this; if (backToCharacterSelection != null) backToCharacterSelection.onClick.AddListener(OnClickBackToCharacterSelection); if (selectCharacterButton != null) selectCharacterButton.onClick.AddListener(OnClickSelectCharacter); if (levelUpCharacter != null) levelUpCharacter.onClick.AddListener(OnClickLevelUp); if (filterCharacterWithQuest != null) filterCharacterWithQuest.onValueChanged.AddListener((val) => ApplyFiltersAndSorting()); if (filterCharacterCanUpgrade != null) filterCharacterCanUpgrade.onValueChanged.AddListener((val) => ApplyFiltersAndSorting()); if (orderByRarity != null) orderByRarity.onValueChanged.AddListener((val) => ApplyFiltersAndSorting()); if (orderByLevel != null) orderByLevel.onValueChanged.AddListener((val) => ApplyFiltersAndSorting()); if (orderByMastery != null) orderByMastery.onValueChanged.AddListener((val) => ApplyFiltersAndSorting()); if (orderByDescending != null) orderByDescending.onValueChanged.AddListener((val) => ApplyFiltersAndSorting()); if (searchByWord != null) searchByWord.onValueChanged.AddListener((val) => ApplyFiltersAndSorting()); if (unlockSkinButton != null) { unlockSkinButton.onClick.AddListener(UnlockSkin); unlockSkinButton.gameObject.SetActive(false); } if (openSkinSelection != null) openSkinSelection.onClick.AddListener(LoadCharacterSkins); } private void OnEnable() { OnOpenMenu.Invoke(); currentLang = LanguageManager.LanguageManager.Instance.GetCurrentLanguage(); ResetFilters(); if (characterSelection != null) characterSelection.SetActive(true); if (characterDetails != null) characterDetails.SetActive(false); DestroyTemporaryModel(); StoreUnlockedCharacters(); ApplyFiltersAndSorting(); } private void OnDisable() { OnCloseMenu.Invoke(); DestroyTemporaryModel(); } private void ResetFilters() { if (filterCharacterWithQuest != null) filterCharacterWithQuest.isOn = false; if (filterCharacterCanUpgrade != null) filterCharacterCanUpgrade.isOn = false; if (searchByWord != null) searchByWord.text = string.Empty; if (orderByRarity != null) orderByRarity.isOn = false; if (orderByLevel != null) orderByLevel.isOn = false; if (orderByMastery != null) orderByMastery.isOn = false; if (orderByDescending != null) orderByDescending.isOn = false; } private void StoreUnlockedCharacters() { unlockedCharacters.Clear(); if (GameInstance.Singleton == null || GameInstance.Singleton.characterData == null) { Debug.LogError("GameInstance or characterData is null."); return; } foreach (CharacterData item in GameInstance.Singleton.characterData) { bool isUnlocked = item.CheckUnlocked || MonetizationManager.Singleton.IsCharacterPurchased(item.characterId.ToString()); if (isUnlocked) unlockedCharacters.Add(item); } } public void ApplyFiltersAndSorting() { List filteredList = new List(unlockedCharacters); if (!string.IsNullOrEmpty(searchByWord.text)) { string lowercaseSearch = searchByWord.text.ToLower(); filteredList = filteredList.FindAll(cd => cd.characterName.ToLower().Contains(lowercaseSearch)); } if (filterCharacterCanUpgrade != null && filterCharacterCanUpgrade.isOn) filteredList = filteredList.FindAll(cd => CharacterCanUpgrade(cd)); if (filterCharacterWithQuest != null && filterCharacterWithQuest.isOn) filteredList = filteredList.FindAll(cd => CharacterHasQuest(cd)); if (orderByRarity != null && orderByRarity.isOn) filteredList.Sort((a, b) => CompareByRarityDescending(a, b)); else if (orderByLevel != null && orderByLevel.isOn) filteredList.Sort((a, b) => PlayerSave.GetCharacterLevel(b.characterId).CompareTo(PlayerSave.GetCharacterLevel(a.characterId))); else if (orderByMastery != null && orderByMastery.isOn) filteredList.Sort((a, b) => PlayerSave.GetCharacterMasteryLevel(b.characterId).CompareTo(PlayerSave.GetCharacterMasteryLevel(a.characterId))); if (orderByDescending != null && orderByDescending.isOn) filteredList.Reverse(); DisplayCharacters(filteredList); } private bool CharacterCanUpgrade(CharacterData cd) { int currentLevel = PlayerSave.GetCharacterLevel(cd.characterId); if (currentLevel >= cd.maxLevel) return false; int nextLevelIndex = currentLevel; if (nextLevelIndex < 0 || nextLevelIndex >= cd.expPerLevel.Length) return false; int currentExp = PlayerSave.GetCharacterCurrentExp(cd.characterId); int expNeeded = cd.expPerLevel[nextLevelIndex]; if (currentExp < expNeeded) return false; if (nextLevelIndex < 0 || nextLevelIndex >= cd.upgradeCostPerLevel.Length) return false; int costNeeded = cd.upgradeCostPerLevel[nextLevelIndex]; int playerCurrency = MonetizationManager.GetCurrency(cd.currencyId); return (playerCurrency >= costNeeded); } private bool CharacterHasQuest(CharacterData cd) { if (GameInstance.Singleton == null || GameInstance.Singleton.questData == null) return false; foreach (QuestItem quest in GameInstance.Singleton.questData) { if (PlayerSave.IsQuestCompleted(quest.questId)) continue; if (quest.requirement == null) continue; if (quest.requirement.targetCharacter != null && quest.requirement.targetCharacter.characterId == cd.characterId) { return true; } } return false; } private int CompareByRarityDescending(CharacterData a, CharacterData b) { Dictionary rarityOrder = new Dictionary() { { CharacterRarity.Legendary, 4 }, { CharacterRarity.Epic, 3 }, { CharacterRarity.Rare, 2 }, { CharacterRarity.Uncommon, 1 }, { CharacterRarity.Common, 0 } }; return rarityOrder[b.characterRarity].CompareTo(rarityOrder[a.characterRarity]); } private void DisplayCharacters(List listToDisplay) { ClearCharacterEntries(); foreach (Transform child in container) Destroy(child.gameObject); characterEntries.Clear(); foreach (CharacterData item in listToDisplay) { try { CharacterEntry entry = Instantiate(characterEntryPrefab, container); string characterNameTranslated = GetTranslatedString(item.characterNameTranslated, item.characterName, currentLang); entry.Setup(item, characterNameTranslated); if (entry.selected != null) entry.selected.gameObject.SetActive(item.characterId == PlayerSave.GetSelectedCharacter()); characterEntries.Add(entry); } catch (System.Exception ex) { Debug.LogError($"Error instantiating character entry for {item.characterName}: {ex.Message}"); } } UpdateFavouriteSelected(PlayerSave.GetSelectedCharacter()); } public void ReloadCharacters() { ApplyFiltersAndSorting(); } public void ShowCharacterDetails(int characterId) { OnOpenCharacterDetails.Invoke(); currentDetailCharacterId = characterId; favouriteCharacterButton.SetCharacterId(currentDetailCharacterId); if (characterSelection != null) characterSelection.SetActive(false); if (characterDetails != null) characterDetails.SetActive(true); UISkins.SetActive(false); unlockSkinButton.gameObject.SetActive(false); if (tempCharacterRendererImage != null) tempCharacterRendererImage.color = Color.white; DestroyTemporaryModel(); CharacterData cd = GetCharacterDataById(characterId); if (cd != null && tempCharacterContainer != null) { int skinIndex = PlayerSave.GetCharacterSkin(cd.characterId); if (cd.characterSkins != null && cd.characterSkins.Length > 0 && skinIndex >= 0 && skinIndex < cd.characterSkins.Length) { CharacterSkin skin = cd.characterSkins[skinIndex]; if (skin.skinCharacterModel != null) tempCharacterModel = Instantiate(skin.skinCharacterModel, tempCharacterContainer); } else if (cd.characterModel != null) { tempCharacterModel = Instantiate(cd.characterModel, tempCharacterContainer); } } UpdateDetailPanel(cd); LoadCharacterSkills(characterId); LoadCharacterUpgrades(characterId); UpdateFavouriteSelected(characterId); uiItemsApply.SetupSlotsForCurrentCharacter(characterId); } public void ShowPreviewSkin(int skinIndex) { if (tempCharacterRendererImage != null) tempCharacterRendererImage.color = Color.grey; DestroyTemporaryModel(); CharacterData cd = GetCharacterDataById(currentDetailCharacterId); if (cd != null && tempCharacterContainer != null) { if (cd.characterSkins != null && cd.characterSkins.Length > 0 && skinIndex >= 0 && skinIndex < cd.characterSkins.Length) { CharacterSkin skin = cd.characterSkins[skinIndex]; if (skin.skinCharacterModel != null) { Instantiate(skin.skinCharacterModel, tempCharacterContainer); } else if (cd.characterModel != null) { Instantiate(cd.characterModel, tempCharacterContainer); } } else if (cd.characterModel != null) { Instantiate(cd.characterModel, tempCharacterContainer); } } } /// /// Updates the UI details panel with character info, including level, EXP and mastery progress. /// /// Character data to display. public void UpdateDetailPanel(CharacterData cd) { if (cd == null) return; string translatedName = GetTranslatedString(cd.characterNameTranslated, cd.characterName, currentLang); string translatedDesc = GetTranslatedString(cd.characterDescriptionTranslated, cd.characterDescription, currentLang); if (characterName != null) characterName.text = translatedName; if (characterDescription != null) characterDescription.text = translatedDesc; // Rarity if (characterRarity != null) { characterRarity.text = GetRarityTranslated(cd.characterRarity.ToString()); ApplyRarityGradient(characterRarity, cd.characterRarity); } if (rarityImage != null) rarityImage.sprite = GetRarityBanner(cd.characterRarity); // Class & Icon string translatedClass = GetTranslatedString(cd.characterClassTranslated, cd.characterClassType, currentLang); if (characterClass != null) characterClass.text = translatedClass; if (characterClassIcon != null) characterClassIcon.sprite = cd.characterClassIcon; if (characterElementalIcon != null) characterElementalIcon.sprite = cd.GetCharacterTypeIcon(); // Level & EXP int lvl = PlayerSave.GetCharacterLevel(cd.characterId); int currExp = PlayerSave.GetCharacterCurrentExp(cd.characterId); if (characterLevel != null) characterLevel.text = $"{lvl}"; if (lvl < cd.maxLevel && lvl < cd.expPerLevel.Length) { int requiredExp = cd.expPerLevel[lvl]; if (characterCurrentExp != null) characterCurrentExp.text = $"{currExp}/{requiredExp}"; if (curentProgressLevelExpBar != null) curentProgressLevelExpBar.fillAmount = (float)currExp / requiredExp; } else { if (characterCurrentExp != null) characterCurrentExp.text = "MAX"; if (curentProgressLevelExpBar != null) curentProgressLevelExpBar.fillAmount = 1f; } // Mastery int currentMasteryLevel = PlayerSave.GetCharacterMasteryLevel(cd.characterId); CharacterMasteryLevel masteryInfo = GameInstance.Singleton.GetMasteryLevel(currentMasteryLevel); string masteryTranslatedName = GetTranslatedString(masteryInfo.masteryNameTranslated, masteryInfo.masteryName, currentLang); if (masteryName != null) masteryName.text = masteryTranslatedName; if (masteryIcon != null) masteryIcon.sprite = masteryInfo.masteryIcon; int currMasteryExp = PlayerSave.GetCharacterCurrentMasteryExp(cd.characterId); // When the character is not at max mastery level (max index is maxMasteryLevel - 1) if (currentMasteryLevel < GameInstance.Singleton.characterMastery.maxMasteryLevel - 1) { int requiredMasteryExp = GameInstance.Singleton.GetMasteryExpForLevel(currentMasteryLevel); if (characterCurrentMasteryExp != null) characterCurrentMasteryExp.text = $"{currMasteryExp}/{requiredMasteryExp}"; if (curentProgressMasteryExpBar != null) curentProgressMasteryExpBar.fillAmount = (float)currMasteryExp / requiredMasteryExp; } else { // When at max mastery level, display 0/MAX (since EXP is reset to 0 on level up) if (characterCurrentMasteryExp != null) characterCurrentMasteryExp.text = $"MAX"; if (curentProgressMasteryExpBar != null) curentProgressMasteryExpBar.fillAmount = 1f; } // Upgrade Price & Currency if (lvl < cd.maxLevel && lvl >= 1 && lvl < cd.expPerLevel.Length) { int costNeeded = cd.upgradeCostPerLevel[lvl]; if (levelUpPrice != null) levelUpPrice.text = costNeeded.ToString(); int playerCurrency = MonetizationManager.GetCurrency(cd.currencyId); if (currencyLevelUpIcon != null) currencyLevelUpIcon.sprite = MonetizationManager.Singleton.GetCurrencyIcon(cd.currencyId); if (levelUpCharacter != null) levelUpCharacter.interactable = (playerCurrency >= costNeeded); } else { if (levelUpPrice != null) levelUpPrice.text = "----"; if (levelUpCharacter != null) levelUpCharacter.interactable = false; } // Now update both Detailed and Basic stats UpdateDetailedStats(); UpdateBasicStats(cd); } /// /// Calculates final stats for display, including base stats (with level-based scaling), /// character upgrades, and equipped item stats. Updates the detailed stat UI. /// All displayed stat values are rounded to an integer. /// /// The character data. /// The current level of the character. public void UpdateDetailedStats() { CharacterData cd = GetCharacterDataById(currentDetailCharacterId); int currentLevel = PlayerSave.GetCharacterLevel(currentDetailCharacterId); if (cd == null) return; // Create a runtime stats object from base stats CharacterStatsRuntime finalStats = new CharacterStatsRuntime(cd.baseStats); // Apply level-based multiplier (e.g. +10% per level above 1) float levelMultiplier = 1f + (cd.statsPercentageIncreaseByLevel * (currentLevel - 1)); if (currentLevel > 1 && levelMultiplier > 1f) { finalStats.baseHP *= levelMultiplier; finalStats.baseHPRegen *= levelMultiplier; finalStats.baseHPLeech *= levelMultiplier; finalStats.baseMP *= levelMultiplier; finalStats.baseMPRegen *= levelMultiplier; finalStats.baseDamage *= levelMultiplier; finalStats.baseAttackSpeed *= levelMultiplier; finalStats.baseCooldownReduction *= levelMultiplier; finalStats.baseCriticalRate *= levelMultiplier; finalStats.baseCriticalDamageMultiplier *= levelMultiplier; finalStats.baseDefense *= levelMultiplier; finalStats.baseShield *= levelMultiplier; finalStats.baseMoveSpeed *= levelMultiplier; finalStats.baseCollectRange *= levelMultiplier; } // Apply character's own stat upgrades (e.g., from "statUpgrades" array) Dictionary upgradeLevels = PlayerSave.LoadAllCharacterUpgradeLevels(cd.characterId); foreach (StatUpgrade upgrade in cd.statUpgrades) { if (upgradeLevels.ContainsKey(upgrade.statType) && upgradeLevels[upgrade.statType] > 0) { finalStats.ApplyStatUpgrade(upgrade.statType, upgradeLevels[upgrade.statType], upgrade); } } // Apply item stats (base + upgrade bonuses) ApplyEquippedItemsStats(cd, finalStats); // Retrieve translated strings for each stat using the current language string _hpTranslated = GetTranslatedString(uiTranslations.hpTranslation, uiTranslations.defaultHp, currentLang); string _hpRegenTranslated = GetTranslatedString(uiTranslations.hpRegenTranslation, uiTranslations.defaultHpRegen, currentLang); string _hpLeechTranslated = GetTranslatedString(uiTranslations.hpLeechTranslation, uiTranslations.defaultHpLeech, currentLang); string _mpTranslated = GetTranslatedString(uiTranslations.mpTranslation, uiTranslations.defaultMp, currentLang); string _mpRegenTranslated = GetTranslatedString(uiTranslations.mpRegenTranslation, uiTranslations.defaultMpRegen, currentLang); string _damageTranslated = GetTranslatedString(uiTranslations.damageTranslation, uiTranslations.defaultDamage, currentLang); string _attackSpeedTranslated = GetTranslatedString(uiTranslations.attackSpeedTranslation, uiTranslations.defaultAttackSpeed, currentLang); string _cooldownReductionTranslated = GetTranslatedString(uiTranslations.cooldownReductionTranslation, uiTranslations.defaultCooldownReduction, currentLang); string _criticalRateTranslated = GetTranslatedString(uiTranslations.criticalRateTranslation, uiTranslations.defaultCriticalRate, currentLang); string _criticalDamageMultiplierTranslated = GetTranslatedString(uiTranslations.criticalDamageMultiplierTranslation, uiTranslations.defaultCriticalDamageMultiplier, currentLang); string _defenseTranslated = GetTranslatedString(uiTranslations.defenseTranslation, uiTranslations.defaultDefense, currentLang); string _shieldTranslated = GetTranslatedString(uiTranslations.shieldTranslation, uiTranslations.defaultShield, currentLang); string _moveSpeedTranslated = GetTranslatedString(uiTranslations.moveSpeedTranslation, uiTranslations.defaultMoveSpeed, currentLang); string _collectRangeTranslated = GetTranslatedString(uiTranslations.collectRangeTranslation, uiTranslations.defaultCollectRange, currentLang); // Display final stats in the detailed UI (rounded to int) if (hpText != null) hpText.text = $"{_hpTranslated} {Mathf.RoundToInt(finalStats.baseHP)}"; if (hpRegenText != null) hpRegenText.text = $"{_hpRegenTranslated}: {Mathf.RoundToInt(finalStats.baseHPRegen)}/s"; if (hpLeechText != null) hpLeechText.text = $"{_hpLeechTranslated}: {Mathf.RoundToInt(finalStats.baseHPLeech)}%"; if (mpText != null) mpText.text = $"{_mpTranslated}: {Mathf.RoundToInt(finalStats.baseMP)}"; if (mpRegenText != null) mpRegenText.text = $"{_mpRegenTranslated}: {Mathf.RoundToInt(finalStats.baseMPRegen)}/s"; if (damageText != null) damageText.text = $"{_damageTranslated}: {Mathf.RoundToInt(finalStats.baseDamage)}"; if (attackSpeedText != null) attackSpeedText.text = $"{_attackSpeedTranslated}: {Mathf.RoundToInt(finalStats.baseAttackSpeed)}"; if (cooldownReductionText != null) cooldownReductionText.text = $"{_cooldownReductionTranslated}: {Mathf.RoundToInt(finalStats.baseCooldownReduction)}%"; if (criticalRateText != null) criticalRateText.text = $"{_criticalRateTranslated}: {Mathf.RoundToInt(finalStats.baseCriticalRate)}%"; if (criticalDamageMultiplierText != null) criticalDamageMultiplierText.text = $"{_criticalDamageMultiplierTranslated}: {Mathf.RoundToInt(finalStats.baseCriticalDamageMultiplier)}x"; if (defenseText != null) defenseText.text = $"{_defenseTranslated}: {Mathf.RoundToInt(finalStats.baseDefense)}"; if (shieldText != null) shieldText.text = $"{_shieldTranslated}: {Mathf.RoundToInt(finalStats.baseShield)}"; if (moveSpeedText != null) moveSpeedText.text = $"{_moveSpeedTranslated}: {Mathf.RoundToInt(finalStats.baseMoveSpeed)}"; if (collectRangeText != null) collectRangeText.text = $"{_collectRangeTranslated}: {Mathf.RoundToInt(finalStats.baseCollectRange)}"; } /// /// Updates the "basic" stats UI (base + bonus) for HP, Damage, MP, etc., /// including the level-based multiplier in the base portion. /// All displayed stat values are rounded to an integer. /// /// The character data. private void UpdateBasicStats(CharacterData cd) { if (cd == null) return; int currentLevel = PlayerSave.GetCharacterLevel(cd.characterId); // Start with raw base stats float baseHP = cd.baseStats.baseHP; float baseDamage = cd.baseStats.baseDamage; float baseMP = cd.baseStats.baseMP; // Apply level-based multiplier to the "base" portion float levelMultiplier = 1f + (cd.statsPercentageIncreaseByLevel * (currentLevel - 1)); if (currentLevel > 1 && levelMultiplier > 1f) { baseHP *= levelMultiplier; baseDamage *= levelMultiplier; baseMP *= levelMultiplier; } // Build final stats for calculating the total (with items + upgrades) CharacterStatsRuntime finalStats = new CharacterStatsRuntime(cd.baseStats); // Also apply the same level-based multiplier to finalStats if (currentLevel > 1 && levelMultiplier > 1f) { finalStats.baseHP *= levelMultiplier; finalStats.baseHPRegen *= levelMultiplier; finalStats.baseHPLeech *= levelMultiplier; finalStats.baseMP *= levelMultiplier; finalStats.baseMPRegen *= levelMultiplier; finalStats.baseDamage *= levelMultiplier; finalStats.baseAttackSpeed *= levelMultiplier; finalStats.baseCooldownReduction *= levelMultiplier; finalStats.baseCriticalRate *= levelMultiplier; finalStats.baseCriticalDamageMultiplier *= levelMultiplier; finalStats.baseDefense *= levelMultiplier; finalStats.baseShield *= levelMultiplier; finalStats.baseMoveSpeed *= levelMultiplier; finalStats.baseCollectRange *= levelMultiplier; } // Apply character upgrades Dictionary upgradeLevels = PlayerSave.LoadAllCharacterUpgradeLevels(cd.characterId); foreach (StatUpgrade upgrade in cd.statUpgrades) { if (upgradeLevels.ContainsKey(upgrade.statType) && upgradeLevels[upgrade.statType] > 0) { finalStats.ApplyStatUpgrade(upgrade.statType, upgradeLevels[upgrade.statType], upgrade); } } // Apply equipped items ApplyEquippedItemsStats(cd, finalStats); // Calculate bonus by comparing finalStats with the new "base" (already scaled by level) float bonusHP = finalStats.baseHP - baseHP; float bonusDamage = finalStats.baseDamage - baseDamage; float bonusMP = finalStats.baseMP - baseMP; // Display base stats (scaled by level, then rounded) and separate bonus (rounded) if (characterHp != null) characterHp.text = Mathf.RoundToInt(finalStats.baseHP).ToString(); if (characterHpBonus != null) characterHpBonus.text = bonusHP > 0 ? $"+{Mathf.RoundToInt(bonusHP)}" : "0"; if (characterDamage != null) characterDamage.text = Mathf.RoundToInt(finalStats.baseDamage).ToString(); if (characterDamageBonus != null) characterDamageBonus.text = bonusDamage > 0 ? $"+{Mathf.RoundToInt(bonusDamage)}" : "0"; if (characterEnergy != null) characterEnergy.text = Mathf.RoundToInt(finalStats.baseMP).ToString(); if (characterEnergyBonus != null) characterEnergyBonus.text = bonusMP > 0 ? $"+{Mathf.RoundToInt(bonusMP)}" : "0"; } /// /// Applies stats from all equipped items (base itemStats + upgrade bonuses) to the provided CharacterStatsRuntime. /// This uses the new uniqueItemGuid approach (or itemId if needed). /// private void ApplyEquippedItemsStats(CharacterData cd, CharacterStatsRuntime finalStats) { // For each slot in this character, find the unique item GUID foreach (string slotName in cd.itemSlots) { // You can store the equipped GUID in PlayerPrefs or a dictionary // Example: string uniqueItemGuid = InventorySave.GetEquippedItemForSlot(cd.characterId, slotName); if (string.IsNullOrEmpty(uniqueItemGuid)) continue; // Find the purchased item data var purchasedItem = MonetizationManager.Singleton.GetPurchasedInventoryItems() .Find(pi => pi.uniqueItemGuid == uniqueItemGuid); if (purchasedItem == null) continue; // not found or error // Map to scriptable var soItem = FindItemById(purchasedItem.itemId); if (soItem == null) continue; // Add base item stats if (soItem.itemStats != null) { finalStats.baseHP += soItem.itemStats.baseHP; finalStats.baseHPRegen += soItem.itemStats.baseHPRegen; finalStats.baseHPLeech += soItem.itemStats.baseHPLeech; finalStats.baseMP += soItem.itemStats.baseMP; finalStats.baseMPRegen += soItem.itemStats.baseMPRegen; finalStats.baseDamage += soItem.itemStats.baseDamage; finalStats.baseAttackSpeed += soItem.itemStats.baseAttackSpeed; finalStats.baseCooldownReduction += soItem.itemStats.baseCooldownReduction; finalStats.baseCriticalRate += soItem.itemStats.baseCriticalRate; finalStats.baseCriticalDamageMultiplier += soItem.itemStats.baseCriticalDamageMultiplier; finalStats.baseDefense += soItem.itemStats.baseDefense; finalStats.baseShield += soItem.itemStats.baseShield; finalStats.baseMoveSpeed += soItem.itemStats.baseMoveSpeed; finalStats.baseCollectRange += soItem.itemStats.baseCollectRange; } // Add item upgrade bonuses int itemLevel = InventorySave.GetItemUpgradeLevel(uniqueItemGuid); float totalUpgrade = 0f; for (int i = 0; i < itemLevel && i < soItem.itemUpgrades.Count; i++) { totalUpgrade += soItem.itemUpgrades[i].statIncreasePercentagePerLevel; } if (totalUpgrade > 0f) { float factor = totalUpgrade; // e.g. 0.2 = +20% // Multiply stats from itemStats finalStats.baseHP += soItem.itemStats.baseHP * factor; finalStats.baseHPRegen += soItem.itemStats.baseHPRegen * factor; finalStats.baseHPLeech += soItem.itemStats.baseHPLeech * factor; finalStats.baseMP += soItem.itemStats.baseMP * factor; finalStats.baseMPRegen += soItem.itemStats.baseMPRegen * factor; finalStats.baseDamage += soItem.itemStats.baseDamage * factor; finalStats.baseAttackSpeed += soItem.itemStats.baseAttackSpeed * factor; finalStats.baseCooldownReduction += soItem.itemStats.baseCooldownReduction * factor; finalStats.baseCriticalRate += soItem.itemStats.baseCriticalRate * factor; finalStats.baseCriticalDamageMultiplier += soItem.itemStats.baseCriticalDamageMultiplier * factor; finalStats.baseDefense += soItem.itemStats.baseDefense * factor; finalStats.baseShield += soItem.itemStats.baseShield * factor; finalStats.baseMoveSpeed += soItem.itemStats.baseMoveSpeed * factor; finalStats.baseCollectRange += soItem.itemStats.baseCollectRange * factor; } } } private InventoryItem FindItemById(string itemId) { if (GameInstance.Singleton == null || GameInstance.Singleton.inventoryItems == null) return null; foreach (InventoryItem item in GameInstance.Singleton.inventoryItems) { if (item.itemId == itemId) return item; } return null; } public void LoadCharacterSkills(int characterId) { foreach (Transform child in containerSkills) Destroy(child.gameObject); if (GameInstance.Singleton == null) { Debug.LogError("GameInstance Singleton is null."); return; } CharacterData cd = GetCharacterDataById(characterId); if (cd == null) { Debug.LogError("Character not found."); return; } if (cd.skills == null || cd.skills.Length == 0) Debug.LogWarning("No skills available for this character."); foreach (SkillData skill in cd.skills) { try { SkillInfoEntry entry = Instantiate(skillInfoPrefab, containerSkills); string _skillName = GetTranslatedString(skill.skillNameTranslated, skill.skillName, currentLang); string _skillDescription = GetTranslatedString(skill.skillDescriptionTranslated, skill.skillDescription, currentLang); entry.SetSkillInfo(skill.icon, _skillName, _skillDescription); } catch (System.Exception ex) { Debug.LogError($"Error instantiating skill entry for {skill.skillName}: {ex.Message}"); } } ApplyCharacterUpgrades(cd); } /// /// Applies the character upgrades and updates the detailed stats UI. /// private void ApplyCharacterUpgrades(CharacterData cd) { if (cd == null) return; Dictionary upgradeLevels = PlayerSave.LoadAllCharacterUpgradeLevels(cd.characterId); // (The method UpdateDetailedStats calls ApplyEquippedItemsStats internally.) UpdateDetailedStats(); } public void LoadCharacterUpgrades(int characterId) { foreach (Transform child in containerUpgrades) Destroy(child.gameObject); if (GameInstance.Singleton == null) { Debug.LogError("GameInstance Singleton is null."); return; } CharacterData cd = GetCharacterDataById(characterId); if (cd == null) { Debug.LogError("Character not found."); return; } Dictionary upgradeLevels = PlayerSave.LoadAllCharacterUpgradeLevels(characterId); foreach (StatUpgrade upgrade in cd.statUpgrades) { int currentLevel = upgradeLevels.ContainsKey(upgrade.statType) ? upgradeLevels[upgrade.statType] : 0; UpgradeEntry entry = Instantiate(upgradeEntryPrefab, containerUpgrades); entry.Initialize(upgrade, upgrade.upgradeIcon, new CharacterStatsRuntime(cd.baseStats), currentLevel, characterId); } } public void ClearCharacterEntries() { foreach (Transform child in container) Destroy(child.gameObject); characterEntries.Clear(); } public async void ChangeSelectedCharacter(int characterId) { PlayerSave.SetSelectedCharacter(characterId); if (uiSkillInfo != null) uiSkillInfo.gameObject.SetActive(false); foreach (CharacterEntry entry in characterEntries) { bool isSelected = (entry.GetCharacterId() == characterId); if (entry.selected != null) entry.selected.gameObject.SetActive(isSelected); } LoadCharacterSkills(characterId); LoadCharacterUpgrades(characterId); UpdateFavouriteSelected(characterId); if (UIMainMenu.Singleton != null) UIMainMenu.Singleton.ChangeSelectedCharacter(characterId); gameObject.SetActive(false); try { await BackendManager.Singleton.UpdateSelectedCharacter(characterId); Debug.Log($"Selected character updated successfully in Firestore: {characterId}"); } catch (System.Exception ex) { Debug.LogError($"Failed to update selected character in Firestore: {ex.Message}"); } } public void LoadCharacterSkins() { CharacterData characterData = GetCharacterDataById(currentDetailCharacterId); UISkins.SetActive(true); if (characterData.characterSkins == null) return; foreach (Transform child in skinsContainer) { Destroy(child.gameObject); } List unlockedSkins = PlayerSave.LoadCharacterUnlockedSkins(currentDetailCharacterId); int index = 0; foreach (CharacterSkin skin in characterData.characterSkins) { bool isUnlocked = skin.isUnlocked || unlockedSkins.Contains(index); SkinEntry skinEntryInstance = Instantiate(skinEntry, skinsContainer); skinEntryInstance.SetupSkinEntry(skin, index, isUnlocked); index++; } } /// /// Changes the character skin if it is already unlocked; otherwise, shows the unlock prompt. /// /// The index of the skin to change to. public void ChangeCharacterSkin(int characterSkinIndex) { UISkins.SetActive(false); currentDetailCharacterSkin = characterSkinIndex; CharacterData characterData = GetCharacterDataById(currentDetailCharacterId); List unlockedSkins = PlayerSave.LoadCharacterUnlockedSkins(currentDetailCharacterId); // Check if the skin is already unlocked either by default or by being in the unlocked skins list. if (characterData.characterSkins[currentDetailCharacterSkin].isUnlocked || unlockedSkins.Contains(currentDetailCharacterSkin)) { PlayerSave.SetCharacterSkin(currentDetailCharacterId, currentDetailCharacterSkin); ShowCharacterDetails(currentDetailCharacterId); UIMainMenu.Singleton.UpdateCharacter(); } else { unlockSkinButton.gameObject.SetActive(true); unlockSkinPrice.text = characterData.characterSkins[currentDetailCharacterSkin].unlockPrice.ToString(); string currencyId = characterData.characterSkins[currentDetailCharacterSkin].unlockCurrencyId; unlockedCurrencyIcon.sprite = MonetizationManager.Singleton.GetCurrencyIcon(currencyId); ShowPreviewSkin(characterSkinIndex); } } /// /// Unlocks the selected skin if the player has sufficient currency. /// public async void UnlockSkin() { CharacterData characterData = GetCharacterDataById(currentDetailCharacterId); if (characterData.characterSkins == null) return; int skinId = currentDetailCharacterSkin; string currencyId = characterData.characterSkins[skinId].unlockCurrencyId; int currencyAmount = MonetizationManager.GetCurrency(currencyId); int requiredAmount = characterData.characterSkins[skinId].unlockPrice; if (currencyAmount >= requiredAmount) { int newAmount = currencyAmount - requiredAmount; MonetizationManager.SetCurrency(currencyId, newAmount); // Load the current list of unlocked skins for this character. List unlockedSkins = PlayerSave.LoadCharacterUnlockedSkins(currentDetailCharacterId); // If the skin is not already unlocked, add it. if (!unlockedSkins.Contains(skinId)) { unlockedSkins.Add(skinId); } // Save the updated unlocked skins list locally. PlayerSave.SaveCharacterUnlockedSkins(currentDetailCharacterId, unlockedSkins); // Set the selected skin for the character. PlayerSave.SetCharacterSkin(currentDetailCharacterId, skinId); ShowCharacterDetails(currentDetailCharacterId); if(BackendManager.Singleton != null) { int level = PlayerSave.GetCharacterLevel(currentDetailCharacterId); int currentExp = PlayerSave.GetCharacterCurrentExp(currentDetailCharacterId); int masteryLevel = PlayerSave.GetCharacterMasteryLevel(currentDetailCharacterId); int masteryExp = PlayerSave.GetCharacterCurrentMasteryExp(currentDetailCharacterId); await BackendManager.Singleton.SaveCharacterUnlockedSkinsAsync(currentDetailCharacterId.ToString(), unlockedSkins); await BackendManager.Singleton.SaveCharacterBasicInfoAsync(currentDetailCharacterId, skinId,level,currentExp,masteryLevel,masteryExp); } return; } DisplayNotEnoughCurrency(); } /// /// Forces a manual update of the stats for the currently displayed character, e.g. after equipping an item. /// public void ForceUpdateCharacterStats() { if (currentDetailCharacterId <= 0) return; CharacterData cd = GetCharacterDataById(currentDetailCharacterId); if (cd == null) return; // Re-run the logic UpdateDetailPanel(cd); LoadCharacterSkills(cd.characterId); LoadCharacterUpgrades(cd.characterId); } private CharacterData GetCharacterDataById(int characterId) { if (GameInstance.Singleton == null || GameInstance.Singleton.characterData == null) return null; foreach (CharacterData item in GameInstance.Singleton.characterData) if (item.characterId == characterId) return item; return null; } public string GetRarityTranslated(string rarity) { switch (rarity) { case "Common": return GetTranslatedString(uiTranslations.CommonRarityTranslated,uiTranslations.CommonRarity,currentLang); case "Uncommon": return GetTranslatedString(uiTranslations.UncommonRarityTranslated, uiTranslations.UncommonRarity, currentLang); case "Rare": return GetTranslatedString(uiTranslations.RareRarityTranslated, uiTranslations.RareRarity, currentLang); case "Epic": return GetTranslatedString(uiTranslations.EpicRarityTranslated, uiTranslations.EpicRarity, currentLang); case "Legendary": return GetTranslatedString(uiTranslations.LegendaryRarityTranslated, uiTranslations.LegendaryRarity, currentLang); default: return "rarity_error_language"; } } public void DisplayUpgradeSucess() { string translatedMessage = GetTranslatedString(uiTranslations.successLevelUpTranslated, uiTranslations.successLevelUp, currentLang); DisplayStatusMessage(translatedMessage, Color.green); } public void DisplayAlreadyMaxLevel() { string translatedMessage = GetTranslatedString(uiTranslations.errorAlreadyMaxLevelTranslated, uiTranslations.errorAlreadyMaxLevel, currentLang); DisplayStatusMessage(translatedMessage, Color.red); } public void DisplayNotEnoughCurrency() { string translatedMessage = GetTranslatedString(uiTranslations.errorNotEnoughCurrencyTranslated, uiTranslations.errorNotEnoughCurrency, currentLang); DisplayStatusMessage(translatedMessage, Color.red); } public void DisplayNotEnoughExp() { string translatedMessage = GetTranslatedString(uiTranslations.errorNotEnoughExpTranslated, uiTranslations.errorNotEnoughExp, currentLang); DisplayStatusMessage(translatedMessage, Color.red); } public void DisplayStatusMessage(string message, Color color) { if (errorMessageUpgrade == null) return; errorMessageUpgrade.text = message; errorMessageUpgrade.color = color; StartCoroutine(HideStatusMessageAfterDelay(errorMessageUpgrade, 1f)); } private IEnumerator HideStatusMessageAfterDelay(TextMeshProUGUI _errorMessage, float delay) { yield return new WaitForSeconds(delay); _errorMessage.text = string.Empty; } private void OnClickSelectCharacter() { ChangeSelectedCharacter(currentDetailCharacterId); } private async void OnClickLevelUp() { CharacterData cd = GetCharacterDataById(currentDetailCharacterId); if (cd == null) return; int currentLevel = PlayerSave.GetCharacterLevel(cd.characterId); if (currentLevel >= cd.maxLevel) { DisplayAlreadyMaxLevel(); return; } int currentExp = PlayerSave.GetCharacterCurrentExp(cd.characterId); int expNeeded = (currentLevel < cd.expPerLevel.Length) ? cd.expPerLevel[currentLevel] : 9999999; if (currentExp < expNeeded) { DisplayNotEnoughExp(); return; } int costNeeded = (currentLevel < cd.upgradeCostPerLevel.Length) ? cd.upgradeCostPerLevel[currentLevel] : 9999999; int playerCurrency = MonetizationManager.GetCurrency(cd.currencyId); if (playerCurrency < costNeeded) { DisplayNotEnoughCurrency(); return; } int newValue = playerCurrency - costNeeded; MonetizationManager.SetCurrency(cd.currencyId, newValue); PlayerSave.SetCharacterCurrentExp(cd.characterId, currentExp - expNeeded); int newLevel = currentLevel + 1; PlayerSave.SetCharacterLevel(cd.characterId, newLevel); string _levelUp = GetTranslatedString(uiTranslations.successLevelUpTranslated, uiTranslations.successLevelUp, currentLang); DisplayStatusMessage($"{_levelUp} : {newLevel}", Color.green); ShowCharacterDetails(cd.characterId); OnCharacterUpgrade.Invoke(); tempCharacterModel.onUpgradeCharacter.Invoke(); if (BackendManager.Singleton != null) { int level = PlayerSave.GetCharacterLevel(currentDetailCharacterId); int _currentExp = PlayerSave.GetCharacterCurrentExp(currentDetailCharacterId); int masteryLevel = PlayerSave.GetCharacterMasteryLevel(currentDetailCharacterId); int masteryExp = PlayerSave.GetCharacterCurrentMasteryExp(currentDetailCharacterId); int skinId = PlayerSave.GetCharacterSkin(currentDetailCharacterId); await BackendManager.Singleton.SaveCharacterBasicInfoAsync(currentDetailCharacterId, skinId, level, _currentExp, masteryLevel, masteryExp); } } private void OnClickBackToCharacterSelection() { DestroyTemporaryModel(); if (characterSelection != null) characterSelection.SetActive(true); if (characterDetails != null) characterDetails.SetActive(false); } private void DestroyTemporaryModel() { if (tempCharacterContainer == null) return; for (int i = tempCharacterContainer.childCount - 1; i >= 0; i--) Destroy(tempCharacterContainer.GetChild(i).gameObject); } public void UpdateFavouriteSelected(int characterId) { int favId = PlayerSave.GetFavouriteCharacter(); if (favouriteSelected != null) favouriteSelected.gameObject.SetActive(characterId == favId); } /// /// Applies a text gradient based on rarity to a TextMeshProUGUI component. /// private void ApplyRarityGradient(TextMeshProUGUI tmpText, CharacterRarity rarity) { string rarityStr = rarity.ToString(); TextRarityGradient gradient; switch (rarityStr) { case "Common": gradient = commonTextColor; break; case "Uncommon": gradient = uncommonTextColor; break; case "Rare": gradient = rareTextColor; break; case "Epic": gradient = epicTextColor; break; case "Legendary": gradient = legendaryTextColor; break; default: gradient = commonTextColor; break; } if (tmpText != null) { tmpText.enableVertexGradient = true; tmpText.colorGradient = new VertexGradient(gradient.topColor, gradient.topColor, gradient.botColor, gradient.botColor); } } private Sprite GetRarityBanner(CharacterRarity rarity) { switch (rarity) { case CharacterRarity.Common: return commonBanner; case CharacterRarity.Uncommon: return uncommonBanner; case CharacterRarity.Rare: return rareBanner; case CharacterRarity.Epic: return epicBanner; case CharacterRarity.Legendary: return legendaryBanner; default: return commonBanner; } } 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; } 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; } public void TestAddMasteryExp(int exp) { GameManager.Singleton.AddCharacterMasteryExp(currentDetailCharacterId, exp); GameManager.Singleton.SaveCharacterBasicInfo(currentDetailCharacterId); UpdateDetailPanel(GetCharacterDataById(currentDetailCharacterId)); } public void TestAddCharacterExp(int exp) { GameManager.Singleton.AddCharacterExp(currentDetailCharacterId, exp); GameManager.Singleton.SaveCharacterBasicInfo(currentDetailCharacterId); UpdateDetailPanel(GetCharacterDataById(currentDetailCharacterId)); } } }