1320 lines
60 KiB
C#
1320 lines
60 KiB
C#
using BulletHellTemplate.Core.Events;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.UI;
|
|
|
|
namespace BulletHellTemplate
|
|
{
|
|
/// <summary>
|
|
/// Handles the UI menu for character selection, filtering, sorting, and detailed character view.
|
|
/// Applies the characterStatsComponent from character base, level upgrades, and equipped items (including upgrade bonuses).
|
|
/// </summary>
|
|
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 characterBuffsComponent/items/runes).")]
|
|
public TextMeshProUGUI characterHpBonus;
|
|
|
|
[Tooltip("Displays the base damage of the character.")]
|
|
public TextMeshProUGUI characterDamage;
|
|
|
|
[Tooltip("Displays additional damage bonus (only from characterBuffsComponent/items/runes).")]
|
|
public TextMeshProUGUI characterDamageBonus;
|
|
|
|
[Tooltip("Displays the base energy/MP of the character.")]
|
|
public TextMeshProUGUI characterEnergy;
|
|
|
|
[Tooltip("Displays additional energy bonus (only from characterBuffsComponent/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<CharacterData> unlockedCharacters = new List<CharacterData>();
|
|
private List<CharacterEntry> characterEntries = new List<CharacterEntry>();
|
|
|
|
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 || PlayerSave.IsCharacterPurchased(item.characterId.ToString());
|
|
if (isUnlocked)
|
|
unlockedCharacters.Add(item);
|
|
}
|
|
}
|
|
|
|
public void ApplyFiltersAndSorting()
|
|
{
|
|
List<CharacterData> filteredList = new List<CharacterData>(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<CharacterRarity, int> rarityOrder = new Dictionary<CharacterRarity, int>()
|
|
{
|
|
{ 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<CharacterData> 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the UI details panel with character info, including level, EXP and mastery progress.
|
|
/// </summary>
|
|
/// <param name="cd">Character data to display.</param>
|
|
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 characterStatsComponent
|
|
UpdateDetailedStats();
|
|
UpdateBasicStats(cd);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Calculates final characterStatsComponent for display, including base characterStatsComponent (with level-based scaling),
|
|
/// character upgrades, and equipped item characterStatsComponent. Updates the detailed stat UI.
|
|
/// All displayed stat values are rounded to an integer.
|
|
/// </summary>
|
|
/// <param name="cd">The character data.</param>
|
|
/// <param name="currentLevel">The current level of the character.</param>
|
|
public void UpdateDetailedStats()
|
|
{
|
|
CharacterData cd = GetCharacterDataById(currentDetailCharacterId);
|
|
int currentLevel = PlayerSave.GetCharacterLevel(currentDetailCharacterId);
|
|
if (cd == null) return;
|
|
|
|
// Create a runtime characterStatsComponent object from base characterStatsComponent
|
|
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<StatType, int> 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 characterStatsComponent (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 characterStatsComponent 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)}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the "basic" characterStatsComponent 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.
|
|
/// </summary>
|
|
/// <param name="cd">The character data.</param>
|
|
private void UpdateBasicStats(CharacterData cd)
|
|
{
|
|
if (cd == null) return;
|
|
|
|
int currentLevel = PlayerSave.GetCharacterLevel(cd.characterId);
|
|
|
|
// Start with raw base characterStatsComponent
|
|
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 characterStatsComponent 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<StatType, int> 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 characterStatsComponent (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";
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Applies characterStatsComponent from all equipped items (base itemStats + upgrade bonuses) to the provided CharacterStatsRuntime.
|
|
/// This uses the new uniqueItemGuid approach (or itemId if needed).
|
|
/// </summary>
|
|
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 = PlayerSave.GetInventoryItems()
|
|
.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 characterStatsComponent
|
|
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 characterStatsComponent 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies the character upgrades and updates the detailed characterStatsComponent UI.
|
|
/// </summary>
|
|
private void ApplyCharacterUpgrades(CharacterData cd)
|
|
{
|
|
if (cd == null) return;
|
|
Dictionary<StatType, int> 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<StatType, int> 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)
|
|
{
|
|
RequestResult ok = await BackendManager.Service.UpdateSelectedCharacterAsync(characterId);
|
|
if (!ok.Success)
|
|
{
|
|
DisplayStatusMessage("Character is not unlocked!", Color.red);
|
|
Debug.LogError($"Failed to update selected character in Backend");
|
|
return;
|
|
}
|
|
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);
|
|
}
|
|
public void LoadCharacterSkins()
|
|
{
|
|
CharacterData characterData = GetCharacterDataById(currentDetailCharacterId);
|
|
UISkins.SetActive(true);
|
|
if (characterData.characterSkins == null) return;
|
|
|
|
foreach (Transform child in skinsContainer)
|
|
{
|
|
Destroy(child.gameObject);
|
|
}
|
|
|
|
List<int> 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++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes the character skin if it is already unlocked; otherwise, shows the unlock prompt.
|
|
/// </summary>
|
|
/// <param name="characterSkinIndex">The index of the skin to change to.</param>
|
|
public async void ChangeCharacterSkin(int characterSkinIndex)
|
|
{
|
|
UISkins.SetActive(false);
|
|
currentDetailCharacterSkin = characterSkinIndex;
|
|
CharacterData characterData = GetCharacterDataById(currentDetailCharacterId);
|
|
RequestResult ok = await BackendManager.Service.UpdateCharacterSkin(currentDetailCharacterId, characterSkinIndex);
|
|
|
|
if (ok.Success)
|
|
{
|
|
ShowCharacterDetails(currentDetailCharacterId);
|
|
UIMainMenu.Singleton.UpdateCharacter();
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlocks the selected skin if the player has sufficient currency.
|
|
/// </summary>
|
|
public async void UnlockSkin()
|
|
{
|
|
CharacterData characterData = GetCharacterDataById(currentDetailCharacterId);
|
|
if (characterData.characterSkins == null)
|
|
return;
|
|
|
|
int skinId = currentDetailCharacterSkin;
|
|
RequestResult ok = await BackendManager.Service.UnlockCharacterSkin(currentDetailCharacterId, currentDetailCharacterSkin);
|
|
|
|
if (ok.Success)
|
|
{
|
|
ShowCharacterDetails(currentDetailCharacterId);
|
|
return;
|
|
}
|
|
DisplayNotEnoughCurrency();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Forces a manual update of the characterStatsComponent for the currently displayed character, e.g. after equipping an item.
|
|
/// </summary>
|
|
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;
|
|
|
|
RequestResult result = await BackendManager.Service.UpdateCharacterLevelUP(currentDetailCharacterId);
|
|
|
|
if (result.Reason == "0")
|
|
{
|
|
DisplayAlreadyMaxLevel();
|
|
return;
|
|
}
|
|
|
|
if (result.Reason == "1")
|
|
{
|
|
DisplayNotEnoughExp();
|
|
return;
|
|
}
|
|
if (result.Reason == "2")
|
|
{
|
|
DisplayNotEnoughCurrency();
|
|
return;
|
|
}
|
|
|
|
string _levelUp = GetTranslatedString(uiTranslations.successLevelUpTranslated, uiTranslations.successLevelUp, currentLang);
|
|
|
|
DisplayStatusMessage($"{_levelUp} : {PlayerSave.GetCharacterLevel(currentDetailCharacterId)}", Color.green);
|
|
ShowCharacterDetails(cd.characterId);
|
|
EventBus.Publish(new MenuCharacterUpgradedEvent(tempCharacterModel));
|
|
|
|
OnCharacterUpgrade?.Invoke();
|
|
tempCharacterModel.OnUpgrade?.Invoke();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies a text gradient based on rarity to a TextMeshProUGUI component.
|
|
/// </summary>
|
|
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 async void TestAddMasteryExp(int exp)
|
|
{
|
|
var result = await BackendManager.Service
|
|
.TestAddCharacterMasteryExpAsync(currentDetailCharacterId, exp);
|
|
|
|
if (result.Success)
|
|
{
|
|
UpdateDetailPanel(GetCharacterDataById(currentDetailCharacterId));
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"Falha ao dar Mastery EXP: {result.Reason}");
|
|
}
|
|
}
|
|
|
|
public async void TestAddCharacterExp(int exp)
|
|
{
|
|
var result = await BackendManager.Service
|
|
.TestAddCharacterExpAsync(currentDetailCharacterId, exp);
|
|
|
|
if (result.Success)
|
|
{
|
|
UpdateDetailPanel(GetCharacterDataById(currentDetailCharacterId));
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"Falha ao dar EXP: {result.Reason}");
|
|
}
|
|
}
|
|
}
|
|
}
|