225 lines
8.1 KiB
C#
Raw Normal View History

2025-09-19 19:43:49 +05:00
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
2025-09-19 14:56:58 +05:00
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
2025-09-19 19:43:49 +05:00
using System.Globalization;
2025-09-19 14:56:58 +05:00
namespace BulletHellTemplate
{
/// <summary>
2025-09-19 19:43:49 +05:00
/// Battle-Pass UI controller. Builds item list, shows XP bar and live season timer.
/// Reads season timing from <see cref="PlayerSave"/> (cached from server at login).
2025-09-19 14:56:58 +05:00
/// </summary>
2025-09-19 19:43:49 +05:00
[DisallowMultipleComponent]
public sealed class UIBattlePass : MonoBehaviour
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
public static UIBattlePass Singleton { get; private set; }
/*──────────── Inspector refs ───────────*/
[Header("Prefabs")]
[SerializeField] private PassEntry passEntryPrefab;
[SerializeField] private Transform containerPassItems;
[Header("HUD")]
[SerializeField] private TextMeshProUGUI errorMessage;
[SerializeField] private TextMeshProUGUI levelText;
[SerializeField] private TextMeshProUGUI xpText;
[SerializeField] private Image xpProgressBar;
[SerializeField] private TextMeshProUGUI currentSeason;
[SerializeField] private TextMeshProUGUI remainingSeasonTime;
[SerializeField] private GameObject ButtonBuyPassVisual;
[Header("Buy-Pass Popup")]
[SerializeField] private GameObject purchasePopup;
[SerializeField] private Image purchaseIconCurrency;
[SerializeField] private TextMeshProUGUI purchaseTextPrice;
2025-09-19 14:56:58 +05:00
[Header("Claim Reward Popup")]
public GameObject claimPopup;
public Image claimIconReward;
public TextMeshProUGUI claimTextRewardAmount;
[Header("Events")]
public UnityEvent OnOpenMenu;
public UnityEvent OnCloseMenu;
2025-09-19 19:43:49 +05:00
/*──────────── Internal ────────────────*/
private CancellationTokenSource cts;
private DateTime seasonStartUtc;
private DateTime seasonEndUtc;
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
/*──────────── Unity ───────────────────*/
private void Awake()
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
if (Singleton == null) Singleton = this;
2025-09-19 14:56:58 +05:00
}
private void OnEnable()
{
2025-09-19 19:43:49 +05:00
BattlePassManager.Singleton.OnXPChanged += RefreshXP;
OnOpenMenu?.Invoke();
_ = ReloadAsync();
2025-09-19 14:56:58 +05:00
}
private void OnDisable()
{
2025-09-19 19:43:49 +05:00
BattlePassManager.Singleton.OnXPChanged -= RefreshXP;
cts?.Cancel();
OnCloseMenu?.Invoke();
2025-09-19 14:56:58 +05:00
}
2025-09-19 19:43:49 +05:00
/*──────────── UI Actions ──────────────*/
public void ShowBuyPopup()
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
purchaseIconCurrency.sprite = MonetizationManager.Singleton.GetCurrencyIcon(GameInstance.Singleton.battlePassCurrencyID);
purchaseTextPrice.text = GameInstance.Singleton.battlePassPrice.ToString();
purchasePopup.SetActive(true);
2025-09-19 14:56:58 +05:00
}
2025-09-19 19:43:49 +05:00
public void HideBuyPopup() => purchasePopup.SetActive(false);
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
public async void ConfirmBuyPass()
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
string err = await BattlePassManager.Singleton.TryUnlockPremiumAsync();
if (string.IsNullOrEmpty(err))
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
HideBuyPopup();
await ReloadAsync();
2025-09-19 14:56:58 +05:00
}
2025-09-19 19:43:49 +05:00
else ShowErrorMessage(err).Forget();
2025-09-19 14:56:58 +05:00
}
2025-09-19 19:43:49 +05:00
/*──────────── Reload / Build ──────────*/
public async UniTask ReloadAsync()
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
Clear();
await BuildEntriesAsync();
await RefreshSeasonAsync();
RefreshXP(null, EventArgs.Empty);
ButtonBuyPassVisual.SetActive(!BattlePassManager.Singleton.IsPremium());
2025-09-19 14:56:58 +05:00
}
2025-09-19 19:43:49 +05:00
private void Clear()
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
foreach (Transform c in containerPassItems) Destroy(c.gameObject);
}
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
private async UniTask BuildEntriesAsync()
{
string lang = LanguageManager.LanguageManager.Instance.GetCurrentLanguage();
for (int i = 0; i < GameInstance.Singleton.battlePassData.Length; i++)
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
var it = GameInstance.Singleton.battlePassData[i];
var e = Instantiate(passEntryPrefab, containerPassItems);
bool premium = BattlePassManager.Singleton.IsPremium();
bool unlocked = premium || it.rewardTier == BattlePassItem.RewardTier.Free;
bool claimed = BattlePassManager.Singleton.IsRewardClaimed(it.passId);
e.SetPassItemInfo(
it, it.passId,
it.GetTranslatedTitle(lang),
it.GetTranslatedDescription(lang),
it.itemIcon,
it.rewardTier == BattlePassItem.RewardTier.Paid,
unlocked, claimed,
i);
2025-09-19 14:56:58 +05:00
}
2025-09-19 19:43:49 +05:00
await UniTask.Yield();
2025-09-19 14:56:58 +05:00
}
2025-09-19 19:43:49 +05:00
/*──────────── XP / Season HUD ─────────*/
public void RefreshXP(object _, EventArgs __)
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
var bp = BattlePassManager.Singleton;
levelText.text = $"Lv. {bp.currentLevel}";
xpText.text = $"{bp.currentXP}/{bp.xpForNextLevel}";
xpProgressBar.fillAmount = bp.xpForNextLevel > 0
? (float)bp.currentXP / bp.xpForNextLevel
: 0f;
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
// Update each PassEntry progress/claim state
foreach (Transform t in containerPassItems)
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
if (t.TryGetComponent<PassEntry>(out var entry))
entry.UpdateProgressBar();
2025-09-19 14:56:58 +05:00
}
}
2025-09-19 19:43:49 +05:00
public void ShowClaimPopup(string title, Sprite icon, string desc)
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
claimIconReward.sprite = icon;
claimTextRewardAmount.text = desc;
claimPopup.SetActive(true);
2025-09-19 14:56:58 +05:00
}
2025-09-19 19:43:49 +05:00
public void HideClaimPopup() => claimPopup.SetActive(false);
2025-09-19 14:56:58 +05:00
/// <summary>
2025-09-19 19:43:49 +05:00
/// Loads cached season info from <see cref="PlayerSave"/> and starts countdown.
2025-09-19 14:56:58 +05:00
/// </summary>
2025-09-19 19:43:49 +05:00
private async UniTask RefreshSeasonAsync()
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
await UniTask.SwitchToMainThread();
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
int season = PlayerSave.GetBattlePassCurrentSeason();
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
if (!PlayerSave.TryGetBattlePassSeasonStartUtc(out seasonStartUtc))
seasonStartUtc = DateTime.UtcNow;
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
if (!PlayerSave.TryGetBattlePassSeasonEndUtc(out seasonEndUtc))
seasonEndUtc = seasonStartUtc.AddDays(30);
2025-09-19 14:56:58 +05:00
2025-09-19 19:43:49 +05:00
currentSeason.text = $"Season {season}";
cts?.Cancel();
cts = new CancellationTokenSource();
CountdownAsync(cts.Token).Forget();
2025-09-19 14:56:58 +05:00
}
/// <summary>
2025-09-19 19:43:49 +05:00
/// Displays live countdown to <see cref="seasonEndUtc"/>.
2025-09-19 14:56:58 +05:00
/// </summary>
2025-09-19 19:43:49 +05:00
private async UniTaskVoid CountdownAsync(CancellationToken token)
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
while (!token.IsCancellationRequested)
{
TimeSpan span = seasonEndUtc - DateTime.UtcNow;
if (span.TotalSeconds <= 0)
{
remainingSeasonTime.text = "Season ended";
PlayerSave.SetSeasonEnded();
break;
}
remainingSeasonTime.text =
$"{span.Days}d {span.Hours}h {span.Minutes}m {span.Seconds:D2}s";
await UniTask.Delay(1000, cancellationToken: token);
}
2025-09-19 14:56:58 +05:00
}
2025-09-19 19:43:49 +05:00
private async UniTaskVoid ShowErrorMessage(string message)
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
errorMessage.text = message;
await UniTask.Delay(1500);
errorMessage.text = "";
2025-09-19 14:56:58 +05:00
}
/// <summary>
2025-09-19 19:43:49 +05:00
/// Debug helper: award XP via backend test endpoint; refreshes UI on success.
2025-09-19 14:56:58 +05:00
/// </summary>
2025-09-19 19:43:49 +05:00
public async void TestEXPPass(int xp)
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
var result = await BackendManager.Service.TestAddBattlePassXpAsync(xp);
if (result.Success)
2025-09-19 14:56:58 +05:00
{
2025-09-19 19:43:49 +05:00
// Reload manager from PlayerSave so level/XP recalc + event dispatch.
BattlePassManager.Singleton.SyncFromPlayerSave();
RefreshXP(null, null);
2025-09-19 14:56:58 +05:00
}
}
}
}