using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; namespace BulletHellTemplate { /// /// Manages the profile menu UI, including displaying player information and managing icon, frame selections, and audio settings. /// public class UIProfileMenu : MonoBehaviour { [Header("UI Elements")] [Tooltip("Text component to display the player's name.")] public TextMeshProUGUI playerName; [Tooltip("Image component to display the player's icon.")] public Image playerIcon; [Tooltip("Image component to display the player's frame.")] public Image playerFrame; [Header("UI Prefabs")] [Tooltip("Prefab for creating icon entries.")] public IconsEntry iconsEntryPrefab; [Tooltip("Prefab for creating frame entries.")] public FramesEntry framesEntryPrefab; [Tooltip("Container to hold dynamically created icon and frame entries.")] public Transform container; [Tooltip("Container GameObject for icons/frames.")] public GameObject containerPref; [Header("Change Name Components")] [Tooltip("Currency ID used for name change.")] public string changeNameTick = "TKN"; [Tooltip("Minimum length for the player's name.")] public int minNameLength = 3; [Tooltip("Maximum length for the player's name.")] public int maxNameLength = 20; [Tooltip("Number of tickets required to change the player's name.")] public int ticketsToChange = 1; [Tooltip("Indicates if a ticket is required for name change.")] public bool needTicket; [Tooltip("UI prefab for the name change input screen.")] public GameObject changeNamePref; [Tooltip("Input field for entering a new name.")] public TMP_InputField changeNameInput; [Tooltip("UI component to display status messages (success/error).")] public TextMeshProUGUI statusText; [Header("Favorite character")] [Tooltip("Image to display mastery icon of the favorite character.")] public Image masteryIcon; [Tooltip("Image fill component to display mastery progress.")] public Image masteryProgressBar; [Tooltip("UI text to display current mastery experience.")] public TextMeshProUGUI currentMasteryExp; [Tooltip("UI text to display the mastery level name.")] public TextMeshProUGUI masteryName; [Tooltip("Container to hold the temporary favorite character model.")] public Transform tempCharacterContainer; [Header("UI Translations")] [Tooltip("Fallback message when there are not enough tickets to change the name.")] public string notEnoughTickets = "Not enough tickets to change the name."; [Tooltip("Translations for the message when there are not enough tickets to change the name.")] public NameTranslatedByLanguage[] notEnoughTicketsTranslated; [Tooltip("Fallback message when name length is invalid.")] public string nameLengthError = "Name must be between 3 and 14 characters."; [Tooltip("Translations for the message when name length is invalid.")] public NameTranslatedByLanguage[] nameLengthErrorTranslated; [Tooltip("Fallback message when the name is already taken.")] public string nameAlreadyTaken = "Name already taken."; [Tooltip("Translations for the message when the name is already taken.")] public NameTranslatedByLanguage[] nameAlreadyTakenTranslated; [Tooltip("Fallback message when the name is changed successfully.")] public string nameChangeSuccess = "Name changed successfully."; [Tooltip("Translations for the message when the name is changed successfully.")] public NameTranslatedByLanguage[] nameChangeSuccessTranslated; [Tooltip("Fallback message when there's an error changing the name.")] public string nameChangeFail = "Error changing name."; [Tooltip("Translations for the message when there's an error changing the name.")] public NameTranslatedByLanguage[] nameChangeFailTranslated; [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 the nickname is successfully changed.")] public UnityEvent OnChangeNickname; [Tooltip("Event invoked when the icon is successfully changed.")] public UnityEvent OnChangeIcon; [Tooltip("Event invoked when the frame is successfully changed.")] public UnityEvent OnChangeFrame; [Tooltip("Event invoked when a coupon is redeemed successfully.")] public UnityEvent OnRedeemCoupon; [Header("Audio Settings")] [Tooltip("Slider for adjusting master volume.")] public Slider masterVolumeSlider; [Tooltip("Slider for adjusting VFX volume.")] public Slider vfxVolumeSlider; [Tooltip("Slider for adjusting ambience volume.")] public Slider ambienceVolumeSlider; private string currentLang; private List iconsEntries = new List(); private List framesEntries = new List(); public static UIProfileMenu Singleton; /// /// Subscribes to language change events and sets up the singleton reference. /// private void Awake() { LanguageManager.LanguageManager.onLanguageChanged += UpdateText; Singleton = this; } /// /// Unsubscribes from language change events. /// private void OnDestroy() { LanguageManager.LanguageManager.onLanguageChanged -= UpdateText; } /// /// Initializes the UI menu when enabled. /// private void OnEnable() { currentLang = LanguageManager.LanguageManager.Instance.GetCurrentLanguage(); OnOpenMenu.Invoke(); DestroyTemporaryModel(); LoadProfile(); LoadFavoriteCharacter(); UpdateText(); SetupSliders(); } /// /// Cleans up the UI menu when disabled. /// private void OnDisable() { DestroyTemporaryModel(); OnCloseMenu.Invoke(); UIMainMenu.Singleton.LoadPlayerInfo(); } /// /// Called when the language is changed, forcing the UI to update translations. /// private void UpdateText() { currentLang = LanguageManager.LanguageManager.Instance.GetCurrentLanguage(); LoadFavoriteCharacter(); } /// /// Loads and displays the player's profile information, including name, icon, and frame. /// public void LoadProfile() { containerPref.SetActive(!containerPref.activeSelf); string name = PlayerSave.GetPlayerName(); playerName.text = !string.IsNullOrEmpty(name) ? name : "Unknown Player"; string iconId = PlayerSave.GetPlayerIcon(); string frameId = PlayerSave.GetPlayerFrame(); SetPlayerIcon(iconId); SetPlayerFrame(frameId); } /// /// Sets initial slider values from the AudioManager (if available) and adds listeners. /// private void SetupSliders() { if (masterVolumeSlider != null) { masterVolumeSlider.onValueChanged.RemoveAllListeners(); masterVolumeSlider.value = AudioManager.Singleton.masterVolume; masterVolumeSlider.onValueChanged.AddListener(OnMasterVolumeChanged); } if (vfxVolumeSlider != null) { vfxVolumeSlider.onValueChanged.RemoveAllListeners(); vfxVolumeSlider.value = AudioManager.Singleton.vfxVolume; vfxVolumeSlider.onValueChanged.AddListener(OnVFXVolumeChanged); } if (ambienceVolumeSlider != null) { ambienceVolumeSlider.onValueChanged.RemoveAllListeners(); ambienceVolumeSlider.value = AudioManager.Singleton.ambienceVolume; ambienceVolumeSlider.onValueChanged.AddListener(OnAmbienceVolumeChanged); } } /// /// Handles changes in the master volume slider. /// /// Volume value from 0 to 1. public void OnMasterVolumeChanged(float value) { AudioManager.Singleton.masterVolume = value; } /// /// Handles changes in the VFX volume slider. /// /// Volume value from 0 to 1. public void OnVFXVolumeChanged(float value) { AudioManager.Singleton.vfxVolume = value; } /// /// Handles changes in the ambience volume slider. /// /// Volume value from 0 to 1. public void OnAmbienceVolumeChanged(float value) { AudioManager.Singleton.ambienceVolume = value; } /// /// Creates and displays icon entries in the container, clearing old entries first. /// public void LoadIcons() { foreach (Transform child in container) { Destroy(child.gameObject); } containerPref.SetActive(true); foreach (IconItem item in GameInstance.Singleton.iconItems) { if (MonetizationManager.Singleton.IsIconPurchased(item.iconId) || item.isUnlocked) { IconsEntry iconEntry = Instantiate(iconsEntryPrefab, container); iconEntry.SetIconInfo(item.iconId); iconEntry.icon.sprite = item.icon; string iconNameTranslatedName = GetTranslatedString(item.iconNameTranslated, item.iconName, currentLang); iconEntry.iconName.text = iconNameTranslatedName; iconsEntries.Add(iconEntry); } } } /// /// Creates and displays frame entries in the container, clearing old entries first. /// public void LoadFrames() { foreach (Transform child in container) { Destroy(child.gameObject); } containerPref.SetActive(true); foreach (FrameItem item in GameInstance.Singleton.frameItems) { if (MonetizationManager.Singleton.IsFramePurchased(item.frameId) || item.isUnlocked) { FramesEntry frameEntry = Instantiate(framesEntryPrefab, container); frameEntry.SetFrameInfo(item.frameId); string frameNameTranslatedName = GetTranslatedString(item.frameNameTranslated,item.frameName, currentLang); frameEntry.icon.sprite = item.icon; frameEntry.frameName.text = frameNameTranslatedName; framesEntries.Add(frameEntry); } } } /// /// Loads the player's favorite character data including mastery progress and 3D model. /// public void LoadFavoriteCharacter() { int favouriteCharacterId = PlayerSave.GetFavouriteCharacter(); int currentMasteryLevel = PlayerSave.GetCharacterMasteryLevel(favouriteCharacterId); int currMasteryExp = PlayerSave.GetCharacterCurrentMasteryExp(favouriteCharacterId); 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; if (currentMasteryLevel < GameInstance.Singleton.characterMastery.maxMasteryLevel) { int requiredMasteryExp = GameInstance.Singleton.GetMasteryExpForLevel(currentMasteryLevel); if (currentMasteryExp != null) currentMasteryExp.text = $"{currMasteryExp}/{requiredMasteryExp}"; if (masteryProgressBar != null) masteryProgressBar.fillAmount = (float)currMasteryExp / requiredMasteryExp; } else { if (currentMasteryExp != null) currentMasteryExp.text = $"{currMasteryExp}/MAX"; if (masteryProgressBar != null) masteryProgressBar.fillAmount = 1f; } foreach (Transform child in tempCharacterContainer) { Destroy(child.gameObject); } CharacterData cd = GetCharacterDataById(favouriteCharacterId); 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) Instantiate(skin.skinCharacterModel, tempCharacterContainer); } else if (cd.characterModel != null) { Instantiate(cd.characterModel, tempCharacterContainer); } } } /// /// Handles the click event to open the name change UI. /// public void OnClickChangeName() { if (needTicket && MonetizationManager.GetCurrency(changeNameTick) < ticketsToChange) { DisplayStatusMessage(GetTranslatedString(notEnoughTicketsTranslated, notEnoughTickets, currentLang), Color.red); return; } changeNamePref.SetActive(true); } /// /// Processes the new name input and starts the coroutine to change the player's name. /// public void ProcessChangeName() { string newName = changeNameInput.text; if (string.IsNullOrEmpty(newName) || newName.Length < minNameLength || newName.Length > maxNameLength) { DisplayStatusMessage(GetTranslatedString(nameLengthErrorTranslated, nameLengthError, currentLang), Color.red); return; } StartCoroutine(ChangePlayerName(newName)); } /// /// Coroutine to change the player's name, including validation and database update. /// /// The new name to set. private IEnumerator ChangePlayerName(string newName) { Task checkNameTask = BackendManager.Singleton.CheckIfNameExists(newName); yield return new WaitUntil(() => checkNameTask.IsCompleted); if (checkNameTask.Result) { DisplayStatusMessage(GetTranslatedString(nameAlreadyTakenTranslated, nameAlreadyTaken, currentLang), Color.red); } else { Task updateNameTask = BackendManager.Singleton.UpdatePlayerName(newName); yield return new WaitUntil(() => updateNameTask.IsCompleted); if (updateNameTask.Exception == null) { DisplayStatusMessage(GetTranslatedString(nameChangeSuccessTranslated, nameChangeSuccess, currentLang), Color.white); playerName.text = newName; UIMainMenu.Singleton.LoadPlayerInfo(); OnChangeNickname.Invoke(); // Invoke event for successful name change if (needTicket) { int currentTickets = MonetizationManager.GetCurrency(changeNameTick); int newTickets = currentTickets - ticketsToChange; MonetizationManager.SetCurrency(changeNameTick, newTickets); } changeNamePref.SetActive(false); } else { DisplayStatusMessage(GetTranslatedString(nameChangeFailTranslated, nameChangeFail, currentLang), Color.red); } } } /// /// Retrieves character data by its ID. /// /// The ID of the character to retrieve. /// Character data if found, otherwise null. 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; } /// /// Destroys any temporary character models currently instantiated. /// private void DestroyTemporaryModel() { if (tempCharacterContainer == null) return; for (int i = tempCharacterContainer.childCount - 1; i >= 0; i--) Destroy(tempCharacterContainer.GetChild(i).gameObject); } /// /// Returns the translated string for the current language, or a fallback value if no translation is found. /// /// Array of NameTranslatedByLanguage entries. /// Fallback text in case the translation is not found. /// The current language ID. /// A translated string if found, otherwise the fallback. private string GetTranslatedString(NameTranslatedByLanguage[] translations, string fallback, string currentLang) { if (translations != null) { foreach (var translation in translations) { if (!string.IsNullOrEmpty(translation.LanguageId) && translation.LanguageId.Equals(currentLang) && !string.IsNullOrEmpty(translation.Translate)) { return translation.Translate; } } } return fallback; } /// /// Displays a status message with the specified color. /// /// The message to display. /// The color of the message text. private void DisplayStatusMessage(string message, Color color) { statusText.text = message; statusText.color = color; StartCoroutine(HideStatusMessageAfterDelay(statusText, 2f)); } /// /// Coroutine to hide the status message after a specified delay. /// /// The UI text component used to display the message. /// Time in seconds before hiding the message. private IEnumerator HideStatusMessageAfterDelay(TextMeshProUGUI statusText, float delay) { yield return new WaitForSeconds(delay); statusText.text = string.Empty; } /// /// Sets the player's icon based on the provided iconId. /// /// The ID of the icon to set. private void SetPlayerIcon(string iconId) { foreach (IconItem item in GameInstance.Singleton.iconItems) { if (item.iconId == iconId) { playerIcon.sprite = item.icon; break; } } } /// /// Sets the player's frame based on the provided frameId. /// /// The ID of the frame to set. private void SetPlayerFrame(string frameId) { foreach (FrameItem item in GameInstance.Singleton.frameItems) { if (item.frameId == frameId) { playerFrame.sprite = item.icon; break; } } } } }