538 lines
21 KiB
C#

using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace BulletHellTemplate
{
/// <summary>
/// Manages the profile menu UI, including displaying player information and managing icon, frame selections, and audio settings.
/// </summary>
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<IconsEntry> iconsEntries = new List<IconsEntry>();
private List<FramesEntry> framesEntries = new List<FramesEntry>();
public static UIProfileMenu Singleton;
/// <summary>
/// Subscribes to language change events and sets up the singleton reference.
/// </summary>
private void Awake()
{
LanguageManager.LanguageManager.onLanguageChanged += UpdateText;
Singleton = this;
}
/// <summary>
/// Unsubscribes from language change events.
/// </summary>
private void OnDestroy()
{
LanguageManager.LanguageManager.onLanguageChanged -= UpdateText;
}
/// <summary>
/// Initializes the UI menu when enabled.
/// </summary>
private void OnEnable()
{
currentLang = LanguageManager.LanguageManager.Instance.GetCurrentLanguage();
OnOpenMenu.Invoke();
DestroyTemporaryModel();
LoadProfile();
LoadFavoriteCharacter();
UpdateText();
SetupSliders();
}
/// <summary>
/// Cleans up the UI menu when disabled.
/// </summary>
private void OnDisable()
{
DestroyTemporaryModel();
OnCloseMenu.Invoke();
UIMainMenu.Singleton.LoadPlayerInfo();
}
/// <summary>
/// Called when the language is changed, forcing the UI to update translations.
/// </summary>
private void UpdateText()
{
currentLang = LanguageManager.LanguageManager.Instance.GetCurrentLanguage();
LoadFavoriteCharacter();
}
/// <summary>
/// Loads and displays the player's profile information, including name, icon, and frame.
/// </summary>
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);
}
/// <summary>
/// Sets initial slider values from the AudioManager (if available) and adds listeners.
/// </summary>
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);
}
}
/// <summary>
/// Handles changes in the master volume slider.
/// </summary>
/// <param name="value">Volume value from 0 to 1.</param>
public void OnMasterVolumeChanged(float value)
{
AudioManager.Singleton.masterVolume = value;
}
/// <summary>
/// Handles changes in the VFX volume slider.
/// </summary>
/// <param name="value">Volume value from 0 to 1.</param>
public void OnVFXVolumeChanged(float value)
{
AudioManager.Singleton.vfxVolume = value;
}
/// <summary>
/// Handles changes in the ambience volume slider.
/// </summary>
/// <param name="value">Volume value from 0 to 1.</param>
public void OnAmbienceVolumeChanged(float value)
{
AudioManager.Singleton.ambienceVolume = value;
}
/// <summary>
/// Creates and displays icon entries in the container, clearing old entries first.
/// </summary>
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);
}
}
}
/// <summary>
/// Creates and displays frame entries in the container, clearing old entries first.
/// </summary>
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);
}
}
}
/// <summary>
/// Loads the player's favorite character data including mastery progress and 3D model.
/// </summary>
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);
}
}
}
/// <summary>
/// Handles the click event to open the name change UI.
/// </summary>
public void OnClickChangeName()
{
if (needTicket && MonetizationManager.GetCurrency(changeNameTick) < ticketsToChange)
{
DisplayStatusMessage(GetTranslatedString(notEnoughTicketsTranslated, notEnoughTickets, currentLang), Color.red);
return;
}
changeNamePref.SetActive(true);
}
/// <summary>
/// Processes the new name input and starts the coroutine to change the player's name.
/// </summary>
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));
}
/// <summary>
/// Coroutine to change the player's name, including validation and database update.
/// </summary>
/// <param name="newName">The new name to set.</param>
private IEnumerator ChangePlayerName(string newName)
{
Task<bool> 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);
}
}
}
/// <summary>
/// Retrieves character data by its ID.
/// </summary>
/// <param name="characterId">The ID of the character to retrieve.</param>
/// <returns>Character data if found, otherwise null.</returns>
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;
}
/// <summary>
/// Destroys any temporary character models currently instantiated.
/// </summary>
private void DestroyTemporaryModel()
{
if (tempCharacterContainer == null) return;
for (int i = tempCharacterContainer.childCount - 1; i >= 0; i--)
Destroy(tempCharacterContainer.GetChild(i).gameObject);
}
/// <summary>
/// Returns the translated string for the current language, or a fallback value if no translation is found.
/// </summary>
/// <param name="translations">Array of NameTranslatedByLanguage entries.</param>
/// <param name="fallback">Fallback text in case the translation is not found.</param>
/// <param name="currentLang">The current language ID.</param>
/// <returns>A translated string if found, otherwise the fallback.</returns>
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;
}
/// <summary>
/// Displays a status message with the specified color.
/// </summary>
/// <param name="message">The message to display.</param>
/// <param name="color">The color of the message text.</param>
private void DisplayStatusMessage(string message, Color color)
{
statusText.text = message;
statusText.color = color;
StartCoroutine(HideStatusMessageAfterDelay(statusText, 2f));
}
/// <summary>
/// Coroutine to hide the status message after a specified delay.
/// </summary>
/// <param name="statusText">The UI text component used to display the message.</param>
/// <param name="delay">Time in seconds before hiding the message.</param>
private IEnumerator HideStatusMessageAfterDelay(TextMeshProUGUI statusText, float delay)
{
yield return new WaitForSeconds(delay);
statusText.text = string.Empty;
}
/// <summary>
/// Sets the player's icon based on the provided iconId.
/// </summary>
/// <param name="iconId">The ID of the icon to set.</param>
private void SetPlayerIcon(string iconId)
{
foreach (IconItem item in GameInstance.Singleton.iconItems)
{
if (item.iconId == iconId)
{
playerIcon.sprite = item.icon;
break;
}
}
}
/// <summary>
/// Sets the player's frame based on the provided frameId.
/// </summary>
/// <param name="frameId">The ID of the frame to set.</param>
private void SetPlayerFrame(string frameId)
{
foreach (FrameItem item in GameInstance.Singleton.frameItems)
{
if (item.frameId == frameId)
{
playerFrame.sprite = item.icon;
break;
}
}
}
}
}