VanguardReMaster/client/Assets/Vanguard/Runtime/UI/StoreView.cs

493 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using Vanguard.Api;
using Vanguard.Api.Contracts;
using Vanguard.Auth;
using Vanguard.Meta;
using Vanguard.State;
namespace Vanguard.UI
{
/// <summary>Магазин в Lobby: бустеры и вторая триал-дека.</summary>
public sealed class StoreView : IDisposable
{
private readonly MetaService _meta;
private readonly AuthService _auth;
private readonly PlayerState _player;
private readonly Action _onBack;
private bool _disposed;
private readonly RectTransform _root;
private readonly Text _balances;
private readonly Text _status;
private readonly Button _goldButton;
private readonly Button _tokenButton;
private readonly RectTransform _trialContent;
private readonly RectTransform _confirm;
private Text _confirmText;
private readonly RectTransform _result;
private Text _resultTitle;
private RectTransform _resultCards;
private StoreResponse _store;
private Func<Task> _confirmAction;
private bool _busy;
private bool _loading;
public StoreView(
Transform parent,
MetaService meta,
AuthService auth,
PlayerState player,
Action onBack)
{
_meta = meta;
_auth = auth;
_player = player;
_onBack = onBack;
_root = UiKit.CreatePanel(parent, "StoreScreen", new Color(0.018f, 0.03f, 0.06f, 0.94f));
var top = UiKit.CreatePanel(_root, "TopBar", new Color(0.06f, 0.08f, 0.13f, 1f));
top.anchorMin = new Vector2(0f, 1f);
top.anchorMax = new Vector2(1f, 1f);
top.pivot = new Vector2(0.5f, 1f);
top.anchoredPosition = Vector2.zero;
top.sizeDelta = new Vector2(0f, 88f);
UiKit.AddHorizontalLayout(top, 22, 16);
var back = UiKit.CreateButton(top, "Back", "← Назад", () => _onBack(), 18);
UiKit.SetPreferredHeight(back, 44f, 0f);
var title = UiKit.CreateText(top, "Title", "Магазин", 32, Color.white, TextAnchor.MiddleLeft);
title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(title, 46f);
_balances = UiKit.CreateText(
top, "Balances", string.Empty, 18,
new Color(0.70f, 0.76f, 0.90f), TextAnchor.MiddleRight);
UiKit.SetPreferredHeight(_balances, 36f);
var body = UiKit.CreatePanel(_root, "Body", new Color(0.02f, 0.03f, 0.06f, 0f));
body.anchorMin = Vector2.zero;
body.anchorMax = Vector2.one;
body.offsetMin = new Vector2(44f, 30f);
body.offsetMax = new Vector2(-44f, -110f);
UiKit.AddVerticalLayout(body, 8, 12);
var booster = UiKit.CreatePanel(body, "Booster", new Color(0.07f, 0.10f, 0.17f, 1f));
UiKit.SetPreferredHeight(booster, 180f);
UiKit.AddVerticalLayout(booster, 20, 8);
var boosterTitle = UiKit.CreateText(
booster, "Title", "Бустер BT01", 26, new Color(0.88f, 0.94f, 1f), TextAnchor.MiddleLeft);
boosterTitle.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(boosterTitle, 34f);
var boosterHint = UiKit.CreateText(
booster,
"Hint",
"5 карт: 4 C + 1 R+ · duplicate protection · 100 Gold или 1 токен.",
18,
new Color(0.60f, 0.68f, 0.84f),
TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(boosterHint, 28f);
var buttons = UiKit.CreatePanel(booster, "Buttons", new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(buttons, 54f);
UiKit.AddHorizontalLayout(buttons, 0, 12);
_goldButton = UiKit.CreateButton(
buttons, "BuyGold", "Купить за 100 Gold", () => AskBooster("gold"), 19);
UiKit.SetPreferredHeight(_goldButton, 52f);
_tokenButton = UiKit.CreateButton(
buttons, "BuyToken", "Купить за 1 токен", () => AskBooster("token"), 19);
UiKit.SetPreferredHeight(_tokenButton, 52f);
var trialTitle = UiKit.CreateText(
body, "TrialTitle", "Триал-деки", 24, Color.white, TextAnchor.MiddleLeft);
trialTitle.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(trialTitle, 34f);
var scroll = UiKit.CreateScrollView(body, "TrialScroll", out _trialContent, 8f);
var scrollElement = scroll.gameObject.AddComponent<LayoutElement>();
scrollElement.flexibleHeight = 1f;
scrollElement.minHeight = 160f;
_status = UiKit.CreateText(
body, "Status", string.Empty, 18,
new Color(0.62f, 0.68f, 0.82f), TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(_status, 34f);
_confirm = BuildConfirm(_root);
_result = BuildResult(_root);
_confirm.gameObject.SetActive(false);
_result.gameObject.SetActive(false);
_root.gameObject.SetActive(false);
_player.Changed += OnPlayerChanged;
}
public void Show()
{
UiKit.SetActive(_root, true);
_confirm.gameObject.SetActive(false);
_result.gameObject.SetActive(false);
OnPlayerChanged();
_ = LoadAsync();
}
public void Hide()
{
_confirm.gameObject.SetActive(false);
_result.gameObject.SetActive(false);
UiKit.SetActive(_root, false);
}
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
_player.Changed -= OnPlayerChanged;
}
public void Tick()
{
}
private async Task LoadAsync()
{
if (_loading)
return;
_loading = true;
SetStatus("Загружаем магазин…", new Color(0.62f, 0.68f, 0.82f));
SetButtons(false);
try
{
await _auth.EnsureAccessAsync();
_store = await _meta.GetStoreAsync();
if (_disposed)
return;
RebuildTrials(_store.TrialDecks);
_goldButton.GetComponentInChildren<Text>().text =
"Купить за " + _store.BoosterPriceGold + " Gold";
SetStatus("Выберите покупку.", new Color(0.62f, 0.68f, 0.82f));
}
catch (ApiException exception)
{
SetStatus(exception.Message, new Color(1f, 0.55f, 0.45f));
}
catch (Exception exception)
{
Debug.LogException(exception);
SetStatus("Не удалось загрузить магазин.", new Color(1f, 0.55f, 0.45f));
}
finally
{
_loading = false;
SetButtons(true);
}
}
private void RebuildTrials(IReadOnlyList<StoreTrialDeckDto> decks)
{
ClearChildren(_trialContent);
if (decks == null || decks.Count == 0)
{
var empty = UiKit.CreateText(_trialContent, "Empty", "Триал-деки недоступны.", 18,
new Color(0.62f, 0.68f, 0.82f));
UiKit.SetPreferredHeight(empty, 80f);
return;
}
foreach (var deck in decks)
AddTrialRow(deck);
}
private void AddTrialRow(StoreTrialDeckDto deck)
{
var row = UiKit.CreatePanel(_trialContent, "Trial_" + deck.Id, new Color(0.07f, 0.09f, 0.14f, 1f));
var rowElement = row.gameObject.AddComponent<LayoutElement>();
rowElement.preferredHeight = 92f;
UiKit.AddHorizontalLayout(row, 16, 14);
var info = UiKit.CreatePanel(row, "Info", new Color(0f, 0f, 0f, 0f));
var infoElement = info.gameObject.AddComponent<LayoutElement>();
infoElement.flexibleWidth = 1f;
infoElement.preferredHeight = 60f;
UiKit.AddVerticalLayout(info, 0, 2);
var name = UiKit.CreateText(
info, "Name", deck.Name, 21, Color.white, TextAnchor.MiddleLeft);
name.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(name, 28f);
var details = UiKit.CreateText(
info,
"Details",
(string.IsNullOrEmpty(deck.Clan) ? "—" : deck.Clan)
+ " · " + deck.CardCount + " карт · "
+ (deck.Owned ? "в коллекции" : "цена " + deck.PriceGold + " Gold"),
16,
new Color(0.60f, 0.68f, 0.84f),
TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(details, 24f);
if (deck.Owned)
{
var owned = UiKit.CreateText(
row, "Owned", "Уже есть", 17,
new Color(0.45f, 0.85f, 0.55f));
var ownedElement = owned.gameObject.AddComponent<LayoutElement>();
ownedElement.preferredWidth = 120f;
ownedElement.preferredHeight = 40f;
}
else
{
var buy = UiKit.CreateButton(
row,
"Buy",
"Купить за " + deck.PriceGold + " Gold",
() => AskTrial(deck),
17);
var buyElement = buy.gameObject.AddComponent<LayoutElement>();
buyElement.preferredWidth = 240f;
buyElement.preferredHeight = 46f;
buyElement.flexibleWidth = 0f;
}
}
private void AskBooster(string payment)
{
if (_store == null || _busy)
return;
var price = payment == "gold"
? _store.BoosterPriceGold + " Gold"
: "1 токен";
AskConfirm(
"Открыть бустер BT01 за " + price + "?",
() => BuyBoosterAsync(payment));
}
private void AskTrial(StoreTrialDeckDto deck)
{
if (_busy)
return;
AskConfirm(
"Купить «" + deck.Name + "» за " + deck.PriceGold + " Gold?",
() => BuyTrialAsync(deck));
}
private void AskConfirm(string text, Func<Task> action)
{
_confirmText.text = text;
_confirmAction = action;
_confirm.SetAsLastSibling();
_confirm.gameObject.SetActive(true);
}
private async void OnConfirmYes()
{
if (_confirmAction == null)
return;
var action = _confirmAction;
_confirmAction = null;
_confirm.gameObject.SetActive(false);
_busy = true;
SetButtons(false);
SetStatus("Выполняем покупку…", new Color(0.62f, 0.68f, 0.82f));
try
{
await action();
}
catch (ApiException exception)
{
SetStatus(exception.Message, new Color(1f, 0.55f, 0.45f));
}
catch (Exception exception)
{
Debug.LogException(exception);
SetStatus("Покупка не удалась.", new Color(1f, 0.55f, 0.45f));
}
finally
{
_busy = false;
SetButtons(true);
}
}
private async Task BuyBoosterAsync(string payment)
{
var result = await _meta.BuyBoosterAsync(payment);
if (_disposed)
return;
SetStatus("Бустер открыт!", new Color(0.45f, 0.85f, 0.55f));
ShowResult(result);
}
private async Task BuyTrialAsync(StoreTrialDeckDto deck)
{
await _meta.BuyTrialDeckAsync(deck.Id);
if (_disposed)
return;
SetStatus("«" + deck.Name + "» добавлена в коллекцию.", new Color(0.45f, 0.85f, 0.55f));
await LoadAsync();
}
private void ShowResult(BoosterResultDto result)
{
ClearChildren(_resultCards);
_resultTitle.text = "Бустер открыт · " + result.Cards.Count + " карт";
foreach (var card in result.Cards)
{
var panel = UiKit.CreatePanel(_resultCards, "Card_" + card.PrintingId, new Color(0.09f, 0.12f, 0.20f, 1f));
var element = panel.gameObject.AddComponent<LayoutElement>();
element.preferredWidth = 210f;
element.preferredHeight = 280f;
element.flexibleWidth = 0f;
UiKit.AddVerticalLayout(panel, 14, 8);
var printing = UiKit.CreateText(
panel, "Printing", card.PrintingId, 18, Color.white);
printing.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(printing, 50f);
var name = UiKit.CreateText(
panel, "Name", card.NameId, 16,
new Color(0.70f, 0.78f, 0.94f));
name.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(name, 60f);
var rarity = UiKit.CreateText(
panel, "Rarity", card.Rarity, 20,
new Color(0.45f, 0.80f, 1f));
UiKit.SetPreferredHeight(rarity, 30f);
if (card.IsNew)
{
var isNew = UiKit.CreateText(
panel, "New", "NEW", 18,
new Color(1f, 0.78f, 0.32f));
isNew.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(isNew, 30f);
}
}
_result.SetAsLastSibling();
_result.gameObject.SetActive(true);
}
private void OnResultClosed()
{
_result.gameObject.SetActive(false);
_ = LoadAsync();
}
private RectTransform BuildConfirm(Transform parent)
{
var overlay = UiKit.CreatePanel(parent, "StoreConfirm", new Color(0f, 0f, 0f, 0.75f));
var card = UiKit.CreateCenteredCard(
overlay, "ConfirmCard", new Vector2(620f, 280f),
new Color(0.08f, 0.10f, 0.16f, 1f));
UiKit.AddVerticalLayout(card, 30, 14);
var title = UiKit.CreateText(card, "Title", "Подтвердите покупку", 27, Color.white);
title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(title, 40f);
_confirmText = UiKit.CreateText(
card, "Text", string.Empty, 19,
new Color(0.72f, 0.78f, 0.92f));
UiKit.SetPreferredHeight(_confirmText, 70f);
var yes = UiKit.CreateButton(card, "Yes", "Купить", OnConfirmYes, 20);
UiKit.SetPreferredHeight(yes, 48f);
var no = UiKit.CreateButton(
card, "No", "Отмена",
() => { _confirmAction = null; _confirm.gameObject.SetActive(false); },
20);
UiKit.SetPreferredHeight(no, 44f);
return overlay;
}
private RectTransform BuildResult(Transform parent)
{
var overlay = UiKit.CreatePanel(parent, "BoosterResult", new Color(0f, 0f, 0f, 0.82f));
var card = UiKit.CreateCenteredCard(
overlay, "ResultCard", new Vector2(1400f, 520f),
new Color(0.06f, 0.08f, 0.14f, 1f));
UiKit.AddVerticalLayout(card, 28, 14);
_resultTitle = UiKit.CreateText(
card, "Title", "Бустер открыт", 28, Color.white);
_resultTitle.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(_resultTitle, 40f);
var row = UiKit.CreatePanel(card, "Cards", new Color(0f, 0f, 0f, 0f));
var rowElement = row.gameObject.AddComponent<LayoutElement>();
rowElement.flexibleHeight = 1f;
rowElement.minHeight = 300f;
UiKit.AddHorizontalLayout(row, 0, 14);
_resultCards = row;
var close = UiKit.CreateButton(card, "Close", "Забрать", OnResultClosed, 21);
UiKit.SetPreferredHeight(close, 52f);
return overlay;
}
private void SetButtons(bool interactable)
{
if (_disposed)
return;
if (_goldButton != null)
_goldButton.interactable = interactable;
if (_tokenButton != null)
_tokenButton.interactable = interactable;
}
private void OnPlayerChanged()
{
if (_disposed || _balances == null)
return;
_balances.text = "Gold " + _player.Gold + " · Пыль " + _player.Dust
+ " · Токены " + _player.Tokens;
}
private void SetStatus(string text, Color color)
{
if (_disposed || _status == null)
return;
_status.text = text ?? string.Empty;
_status.color = color;
}
private static void ClearChildren(Transform parent)
{
for (var i = parent.childCount - 1; i >= 0; i--)
UnityEngine.Object.Destroy(parent.GetChild(i).gameObject);
}
}
}