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

889 lines
34 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.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using Vanguard.Api;
using Vanguard.Api.Contracts;
using Vanguard.Auth;
using Vanguard.Meta;
namespace Vanguard.UI
{
/// <summary>
/// Простой декбилдер: коллекция слева, текущая колода справа,
/// фильтры в оверлее, статистика по триггерам и грейдам.
/// </summary>
public sealed class DeckBuilderView
{
private readonly MetaService _meta;
private readonly AuthService _auth;
private readonly Action _onBack;
private readonly Action<DeckDto> _onPlay;
private readonly RectTransform _root;
private readonly RectTransform _listView;
private readonly RectTransform _editorView;
private RectTransform _listContent;
private RectTransform _collectionContent;
private RectTransform _deckContent;
private InputField _nameInput;
private Text _listStatus;
private Text _editorStatus;
private Text _stats;
private Text _deckCount;
private Text _filterSummary;
private RectTransform _filterOverlay;
private RectTransform _clanContent;
private RectTransform _confirmOverlay;
private Text _confirmText;
private DeckDto _pendingDelete;
private readonly List<DeckDto> _decks = new List<DeckDto>();
private readonly List<CollectionItemDto> _collection = new List<CollectionItemDto>();
private readonly Dictionary<string, CardCatalogItemDto> _catalog =
new Dictionary<string, CardCatalogItemDto>(StringComparer.Ordinal);
private readonly List<string> _cards = new List<string>();
private readonly Dictionary<string, Text> _collectionCountTexts =
new Dictionary<string, Text>(StringComparer.Ordinal);
private DeckDto _editing;
private string _filterClan = "Все";
private string _filterTrigger = "Все";
private int _filterGrade = -1;
private bool _loading;
private bool _busy;
public DeckBuilderView(
Transform parent,
MetaService meta,
AuthService auth,
Action onBack,
Action<DeckDto> onPlay)
{
_meta = meta;
_auth = auth;
_onBack = onBack;
_onPlay = onPlay;
_root = UiKit.CreatePanel(parent, "DeckBuilderScreen", new Color(0.018f, 0.03f, 0.06f, 0.97f));
_listView = BuildListView();
_editorView = BuildEditorView();
_filterOverlay = BuildFilterOverlay();
_confirmOverlay = BuildDeleteConfirm();
_listView.gameObject.SetActive(false);
_editorView.gameObject.SetActive(false);
_filterOverlay.gameObject.SetActive(false);
_confirmOverlay.gameObject.SetActive(false);
_root.gameObject.SetActive(false);
}
public void Show()
{
UiKit.SetActive(_root, true);
ShowList();
_ = LoadAsync();
}
public void Hide()
{
_filterOverlay.gameObject.SetActive(false);
_confirmOverlay.gameObject.SetActive(false);
_root.gameObject.SetActive(false);
}
private RectTransform BuildListView()
{
var view = UiKit.CreatePanel(_root, "DeckList", new Color(0.018f, 0.03f, 0.06f, 0.97f));
var top = BuildTopBar(view, "Колоды", out _);
var create = UiKit.CreateButton(top, "Create", "Создать колоду", NewDeck, 19);
UiKit.SetPreferredHeight(create, 46f, 0f);
var body = UiKit.CreatePanel(view, "Body", new Color(0f, 0f, 0f, 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, 10);
var hint = UiKit.CreateText(
body,
"Hint",
"Откройте колоду для редактирования или создайте новую. «В бой» ставит колоду в очередь.",
17,
new Color(0.60f, 0.68f, 0.84f),
TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(hint, 26f);
var scroll = UiKit.CreateScrollView(body, "DeckListScroll", out _listContent, 8f);
var element = scroll.gameObject.AddComponent<LayoutElement>();
element.flexibleHeight = 1f;
element.minHeight = 200f;
_listStatus = UiKit.CreateText(
body, "Status", string.Empty, 18,
new Color(0.62f, 0.68f, 0.82f), TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(_listStatus, 34f);
return view;
}
private RectTransform BuildEditorView()
{
var view = UiKit.CreatePanel(_root, "DeckEditor", new Color(0.018f, 0.03f, 0.06f, 0.97f));
var top = UiKit.CreatePanel(view, "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, 18, 12);
var back = UiKit.CreateButton(top, "Back", "← Колоды", BackToCards, 18);
UiKit.SetPreferredHeight(back, 44f, 0f);
var title = UiKit.CreateText(top, "Title", "Конструктор", 28, Color.white, TextAnchor.MiddleLeft);
title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(title, 44f, 0f);
_nameInput = UiKit.CreateInput(top, "Name", "Название колоды");
UiKit.SetPreferredHeight(_nameInput, 46f);
var save = UiKit.CreateButton(top, "Save", "Сохранить", SaveDeck, 18);
UiKit.SetPreferredHeight(save, 44f, 0f);
var body = UiKit.CreatePanel(view, "Body", new Color(0f, 0f, 0f, 0f));
body.anchorMin = Vector2.zero;
body.anchorMax = Vector2.one;
body.offsetMin = new Vector2(34f, 30f);
body.offsetMax = new Vector2(-34f, -106f);
UiKit.AddHorizontalLayout(body, 0, 14);
var left = UiKit.CreatePanel(body, "CollectionArea", new Color(0.04f, 0.06f, 0.10f, 0.7f));
var leftElement = left.gameObject.AddComponent<LayoutElement>();
leftElement.flexibleWidth = 1f;
leftElement.flexibleHeight = 1f;
leftElement.minHeight = 300f;
UiKit.AddVerticalLayout(left, 10, 6);
var filterRow = UiKit.CreatePanel(left, "FilterRow", new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(filterRow, 48f);
UiKit.AddHorizontalLayout(filterRow, 0, 10);
var filters = UiKit.CreateButton(filterRow, "Filters", "Фильтры", OpenFilters, 18);
UiKit.SetPreferredHeight(filters, 44f, 0f);
var reset = UiKit.CreateButton(filterRow, "ResetFilters", "Сбросить фильтры", ResetFilters, 17);
UiKit.SetPreferredHeight(reset, 44f, 0f);
_filterSummary = UiKit.CreateText(
filterRow, "FilterSummary", "Фильтры: все", 16,
new Color(0.60f, 0.68f, 0.84f), TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(_filterSummary, 44f);
var statsPanel = UiKit.CreatePanel(left, "Stats", new Color(0.06f, 0.08f, 0.13f, 1f));
UiKit.SetPreferredHeight(statsPanel, 76f);
UiKit.AddVerticalLayout(statsPanel, 8, 2);
_stats = UiKit.CreateText(
statsPanel, "Text", string.Empty, 16,
new Color(0.72f, 0.78f, 0.92f), TextAnchor.MiddleLeft);
_stats.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(_stats, 56f);
var gridScroll = UiKit.CreateGridScrollView(
left,
"CollectionGrid",
new Vector2(166f, 186f),
new Vector2(8f, 8f),
out _collectionContent);
var gridElement = gridScroll.gameObject.AddComponent<LayoutElement>();
gridElement.flexibleHeight = 1f;
gridElement.minHeight = 220f;
var right = UiKit.CreatePanel(body, "DeckArea", new Color(0.04f, 0.06f, 0.10f, 0.85f));
var rightElement = right.gameObject.AddComponent<LayoutElement>();
rightElement.preferredWidth = 430f;
rightElement.minWidth = 360f;
rightElement.flexibleWidth = 0f;
rightElement.flexibleHeight = 1f;
rightElement.minHeight = 300f;
UiKit.AddVerticalLayout(right, 10, 6);
_deckCount = UiKit.CreateText(
right, "DeckCount", "Колода: 0/50", 20, Color.white, TextAnchor.MiddleLeft);
_deckCount.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(_deckCount, 30f);
var deckScroll = UiKit.CreateHorizontalScrollView(
right, "DeckScroll", out _deckContent, 6f);
var deckElement = deckScroll.gameObject.AddComponent<LayoutElement>();
deckElement.flexibleHeight = 1f;
deckElement.minHeight = 220f;
var editorStatusPanel = UiKit.CreatePanel(right, "StatusPanel", new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(editorStatusPanel, 54f);
_editorStatus = UiKit.CreateText(
editorStatusPanel, "Status", string.Empty, 16,
new Color(0.62f, 0.68f, 0.82f), TextAnchor.UpperLeft);
_editorStatus.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(_editorStatus, 50f);
return view;
}
private RectTransform BuildFilterOverlay()
{
var overlay = UiKit.CreatePanel(
_editorView, "FilterOverlay", new Color(0f, 0f, 0f, 0.78f));
var card = UiKit.CreateCenteredCard(
overlay, "FilterCard", new Vector2(860f, 820f),
new Color(0.06f, 0.08f, 0.13f, 1f));
UiKit.AddVerticalLayout(card, 26, 10);
var title = UiKit.CreateText(card, "Title", "Фильтры коллекции", 28, Color.white);
title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(title, 40f);
AddFilterSection(card, "Ранг", new[] { "Все", "0", "1", "2", "3" }, OnGradeFilter);
AddFilterSection(card, "Триггер", new[] { "Все", "None", "Critical", "Draw", "Stand", "Heal" }, OnTriggerFilter);
var clanTitle = UiKit.CreateText(
card, "ClanTitle", "Клан", 17, new Color(0.66f, 0.74f, 0.90f), TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(clanTitle, 24f);
var clanScroll = UiKit.CreateScrollView(card, "ClanScroll", out _clanContent, 4f);
var clanElement = clanScroll.gameObject.AddComponent<LayoutElement>();
clanElement.flexibleHeight = 1f;
clanElement.minHeight = 150f;
var close = UiKit.CreateButton(card, "Close", "Готово", CloseFilters, 20);
UiKit.SetPreferredHeight(close, 48f);
return overlay;
}
private void RebuildClanFilters()
{
ClearChildren(_clanContent);
AddClanButton(_clanContent, "Все");
foreach (var clan in _catalog.Values.Select(item => item.Clan).Distinct().OrderBy(item => item))
AddClanButton(_clanContent, clan);
}
private RectTransform BuildDeleteConfirm()
{
var overlay = UiKit.CreatePanel(
_root, "DeleteDeckConfirm", new Color(0f, 0f, 0f, 0.78f));
var card = UiKit.CreateCenteredCard(
overlay, "ConfirmCard", new Vector2(640f, 300f),
new Color(0.06f, 0.08f, 0.13f, 1f));
UiKit.AddVerticalLayout(card, 30, 12);
var title = UiKit.CreateText(card, "Title", "Удалить колоду?", 28, Color.white);
title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(title, 42f);
_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", "Удалить", ConfirmDelete, 20);
UiKit.SetPreferredHeight(yes, 48f);
var no = UiKit.CreateButton(card, "No", "Отмена", CancelDelete, 20);
UiKit.SetPreferredHeight(no, 44f);
return overlay;
}
private void RequestDelete(DeckDto deck)
{
_pendingDelete = deck;
_confirmText.text = deck.Name + "\nКолода будет удалена без восстановления.";
_confirmOverlay.SetAsLastSibling();
_confirmOverlay.gameObject.SetActive(true);
}
private void CancelDelete()
{
_pendingDelete = null;
_confirmOverlay.gameObject.SetActive(false);
}
private void ConfirmDelete()
{
var deck = _pendingDelete;
_confirmOverlay.gameObject.SetActive(false);
_pendingDelete = null;
if (deck != null)
DeleteDeck(deck);
}
private void AddFilterSection(Transform parent, string title, string[] labels, Action<string> onSelected)
{
var caption = UiKit.CreateText(
parent, "Caption_" + title, title, 17,
new Color(0.66f, 0.74f, 0.90f), TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(caption, 24f);
var row = UiKit.CreatePanel(parent, "Row_" + title, new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(row, 44f);
UiKit.AddHorizontalLayout(row, 0, 6);
foreach (var label in labels)
{
var captured = label;
var button = UiKit.CreateButton(
row, "Filter_" + label, label, () => onSelected(captured), 16);
UiKit.SetPreferredHeight(button, 40f);
}
}
private void AddClanButton(Transform parent, string clan)
{
var captured = clan;
var button = UiKit.CreateButton(
parent, "Clan_" + clan, clan, () => OnClanFilter(captured), 16);
UiKit.SetPreferredHeight(button, 40f);
}
private RectTransform BuildTopBar(RectTransform view, string titleText, out Text title)
{
var top = UiKit.CreatePanel(view, "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, 18, 12);
var back = UiKit.CreateButton(top, "Back", "← В лобби", () => _onBack(), 18);
UiKit.SetPreferredHeight(back, 44f, 0f);
title = UiKit.CreateText(top, "Title", titleText, 30, Color.white, TextAnchor.MiddleLeft);
title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(title, 46f);
return top;
}
private async Task LoadAsync()
{
if (_loading)
return;
_loading = true;
_listStatus.text = "Загружаем коллекцию и колоды…";
_listStatus.color = new Color(0.62f, 0.68f, 0.82f);
ClearChildren(_listContent);
try
{
await _auth.EnsureAccessAsync();
var collection = await _meta.GetCollectionAsync();
var catalog = await _meta.GetCatalogAsync();
var decks = await _meta.GetDecksAsync();
if (!_root.gameObject.activeSelf)
return;
_collection.Clear();
_collection.AddRange(collection.Items ?? new List<CollectionItemDto>());
_catalog.Clear();
foreach (var item in catalog)
_catalog[item.PrintingId] = item;
RebuildClanFilters();
_decks.Clear();
_decks.AddRange(decks);
BuildDeckList();
_listStatus.text = _decks.Count == 0
? "Колод пока нет. Создайте первую."
: "Колод: " + _decks.Count + ".";
}
catch (ApiException exception)
{
_listStatus.text = exception.Message;
_listStatus.color = new Color(1f, 0.55f, 0.45f);
}
catch (Exception exception)
{
Debug.LogException(exception);
_listStatus.text = "Не удалось загрузить декбилдер.";
_listStatus.color = new Color(1f, 0.55f, 0.45f);
}
finally
{
_loading = false;
}
}
private void BuildDeckList()
{
ClearChildren(_listContent);
if (_decks.Count == 0)
return;
foreach (var deck in _decks)
{
var row = UiKit.CreatePanel(
_listContent, "Deck_" + deck.Id, new Color(0.07f, 0.09f, 0.14f, 1f));
var rowElement = row.gameObject.AddComponent<LayoutElement>();
rowElement.preferredHeight = 82f;
UiKit.AddHorizontalLayout(row, 14, 10);
var name = UiKit.CreateText(
row, "Name", deck.Name, 21, Color.white, TextAnchor.MiddleLeft);
name.fontStyle = FontStyle.Bold;
var nameElement = name.gameObject.AddComponent<LayoutElement>();
nameElement.flexibleWidth = 1f;
nameElement.preferredHeight = 40f;
var info = UiKit.CreateText(
row, "Info", deck.Cards.Count + " карт · " + deck.Format, 16,
new Color(0.60f, 0.68f, 0.84f), TextAnchor.MiddleLeft);
var infoElement = info.gameObject.AddComponent<LayoutElement>();
infoElement.preferredWidth = 190f;
infoElement.preferredHeight = 36f;
infoElement.flexibleWidth = 0f;
var edit = UiKit.CreateButton(row, "Edit", "Открыть", () => OpenDeck(deck), 17);
var editElement = edit.gameObject.AddComponent<LayoutElement>();
editElement.preferredWidth = 120f;
editElement.preferredHeight = 44f;
editElement.flexibleWidth = 0f;
var play = UiKit.CreateButton(row, "Play", "В бой", () => _onPlay(deck), 17);
var playElement = play.gameObject.AddComponent<LayoutElement>();
playElement.preferredWidth = 100f;
playElement.preferredHeight = 44f;
playElement.flexibleWidth = 0f;
var delete = UiKit.CreateButton(row, "Delete", "Удалить", () => RequestDelete(deck), 16);
var deleteElement = delete.gameObject.AddComponent<LayoutElement>();
deleteElement.preferredWidth = 105f;
deleteElement.preferredHeight = 44f;
deleteElement.flexibleWidth = 0f;
}
}
private void NewDeck()
{
_editing = null;
_cards.Clear();
_nameInput.text = "Новая колода";
ShowEditor();
}
private void OpenDeck(DeckDto deck)
{
_editing = deck;
_cards.Clear();
_cards.AddRange(deck.Cards);
_nameInput.text = deck.Name;
ShowEditor();
}
private void ShowEditor()
{
_listView.gameObject.SetActive(false);
_editorView.gameObject.SetActive(true);
_nameInput.text = _editing?.Name ?? "Новая колода";
_deckCount.text = "Колода: " + _cards.Count + "/50";
_editorStatus.text = string.Empty;
RebuildEditor();
}
private void BackToCards()
{
_filterOverlay.gameObject.SetActive(false);
_confirmOverlay.gameObject.SetActive(false);
_editorView.gameObject.SetActive(false);
_listView.gameObject.SetActive(true);
BuildDeckList();
}
private void RebuildEditor()
{
UpdateStats();
RebuildDeck();
RebuildCollection();
_deckCount.text = "Колода: " + _cards.Count + "/50";
}
private void RebuildCollection()
{
ClearChildren(_collectionContent);
_collectionCountTexts.Clear();
if (_catalog.Count == 0)
return;
foreach (var item in _collection.OrderBy(item => GradeOf(item)).ThenBy(item => item.NameEn, StringComparer.Ordinal))
{
if (!_catalog.TryGetValue(item.PrintingId, out var meta))
continue;
if (_filterGrade >= 0 && meta.Grade != _filterGrade)
continue;
if (_filterTrigger != "Все" && meta.Trigger != _filterTrigger)
continue;
if (_filterClan != "Все" && meta.Clan != _filterClan)
continue;
AddCollectionCell(item, meta);
}
}
private void AddCollectionCell(CollectionItemDto item, CardCatalogItemDto meta)
{
var cell = UiKit.CreatePanel(
_collectionContent, "Owned_" + item.PrintingId, new Color(0.08f, 0.11f, 0.17f, 1f));
var button = cell.gameObject.AddComponent<Button>();
var image = cell.GetComponent<Image>();
button.targetGraphic = image;
button.onClick.AddListener(() => AddCard(item));
UiKit.AddVerticalLayout(cell, 6, 2);
var name = UiKit.CreateText(
cell, "Name", item.NameEn, 14, Color.white, TextAnchor.UpperLeft);
name.fontStyle = FontStyle.Bold;
name.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(name, 34f);
var printing = UiKit.CreateText(
cell, "Printing", item.PrintingId, 11,
new Color(0.56f, 0.64f, 0.78f), TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(printing, 16f);
var info = UiKit.CreateText(
cell, "Info", "G" + meta.Grade + " · " + meta.Trigger + " · " + meta.Clan,
12,
new Color(0.66f, 0.74f, 0.90f),
TextAnchor.MiddleLeft);
info.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(info, 30f);
var counts = UiKit.CreateText(
cell,
"Counts",
meta.Rarity + " · есть " + item.Count + " · в колоде " + UsedByName(item.NameId),
12,
new Color(0.56f, 0.64f, 0.78f),
TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(counts, 16f);
_collectionCountTexts[item.PrintingId] = counts;
}
private void RebuildDeck()
{
ClearChildren(_deckContent);
if (_cards.Count == 0)
{
var empty = UiKit.CreateText(
_deckContent, "Empty", "Колода пуста", 16,
new Color(0.62f, 0.68f, 0.82f));
UiKit.SetPreferredHeight(empty, 120f);
return;
}
foreach (var group in _cards.GroupBy(card => card).OrderBy(group => group.Key))
{
var printingId = group.Key;
var count = group.Count();
var name = _catalog.TryGetValue(printingId, out var meta)
? meta.NameEn
: printingId;
var cell = UiKit.CreatePanel(
_deckContent, "Deck_" + printingId, new Color(0.09f, 0.12f, 0.20f, 1f));
var element = cell.gameObject.AddComponent<LayoutElement>();
element.preferredWidth = 142f;
element.preferredHeight = 190f;
element.flexibleWidth = 0f;
UiKit.AddVerticalLayout(cell, 6, 2);
var nameText = UiKit.CreateText(
cell, "Name", name, 13, Color.white, TextAnchor.UpperLeft);
nameText.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(nameText, 38f);
var idText = UiKit.CreateText(
cell, "Printing", printingId, 10,
new Color(0.56f, 0.64f, 0.78f));
UiKit.SetPreferredHeight(idText, 16f);
var info = UiKit.CreateText(
cell,
"Info",
"×" + count + (meta != null ? " · G" + meta.Grade + " · " + meta.Trigger : string.Empty),
12,
new Color(0.70f, 0.78f, 0.94f));
UiKit.SetPreferredHeight(info, 22f);
var remove = UiKit.CreateButton(
cell, "Remove", "−", () => RemoveCard(printingId), 20);
UiKit.SetPreferredHeight(remove, 38f);
}
}
private void AddCard(CollectionItemDto item)
{
if (!_catalog.TryGetValue(item.PrintingId, out var meta))
return;
if (_cards.Count >= 50)
{
SetEditorStatus("В колоде уже 50 карт.");
return;
}
if (UsedByName(item.NameId) >= 4)
{
SetEditorStatus("Не больше 4 копий одной карты.");
return;
}
if (_cards.Count(card => card == item.PrintingId) >= item.Count)
{
SetEditorStatus("В коллекции больше нет свободных копий.");
return;
}
_cards.Add(item.PrintingId);
SetEditorStatus(string.Empty);
RefreshAfterDeckChange();
}
private void RemoveCard(string printingId)
{
var index = _cards.LastIndexOf(printingId);
if (index >= 0)
_cards.RemoveAt(index);
RefreshAfterDeckChange();
}
private void RefreshAfterDeckChange()
{
UpdateStats();
RebuildDeck();
RefreshCollectionCounts();
_deckCount.text = "Колода: " + _cards.Count + "/50";
}
private void RefreshCollectionCounts()
{
foreach (var pair in _collectionCountTexts)
{
var item = _collection.Find(candidate => candidate.PrintingId == pair.Key);
if (item == null || !_catalog.TryGetValue(pair.Key, out var meta))
continue;
pair.Value.text = meta.Rarity + " · есть " + item.Count
+ " · в колоде " + UsedByName(item.NameId);
}
}
private void UpdateStats()
{
var triggers = new Dictionary<string, int>(StringComparer.Ordinal)
{
["—"] = 0, ["K"] = 0, ["D"] = 0, ["S"] = 0, ["H"] = 0,
};
var grades = new int[4];
foreach (var printingId in _cards)
{
if (!_catalog.TryGetValue(printingId, out var meta))
continue;
if (meta.Grade >= 0 && meta.Grade < grades.Length)
grades[meta.Grade]++;
var key = meta.Trigger switch
{
"Critical" => "K",
"Draw" => "D",
"Stand" => "S",
"Heal" => "H",
_ => "—",
};
triggers[key]++;
}
_stats.text = "Триггеры: — " + triggers["—"] + " · K " + triggers["K"]
+ " · D " + triggers["D"] + " · S " + triggers["S"] + " · H " + triggers["H"]
+ "\nGrade: 0 — " + grades[0] + " · 1 — " + grades[1]
+ " · 2 — " + grades[2] + " · 3 — " + grades[3];
}
private int UsedByName(string nameId) =>
_cards.Count(printingId =>
_catalog.TryGetValue(printingId, out var meta) && meta.NameId == nameId);
private int GradeOf(CollectionItemDto item) =>
_catalog.TryGetValue(item.PrintingId, out var meta) ? meta.Grade : int.MaxValue;
private void OpenFilters()
{
_filterOverlay.SetAsLastSibling();
_filterOverlay.gameObject.SetActive(true);
}
private void CloseFilters()
{
_filterOverlay.gameObject.SetActive(false);
}
private void OnGradeFilter(string value)
{
_filterGrade = value == "Все" ? -1 : int.Parse(value);
UpdateFilterSummary();
RebuildCollection();
}
private void OnTriggerFilter(string value)
{
_filterTrigger = value;
UpdateFilterSummary();
RebuildCollection();
}
private void OnClanFilter(string value)
{
_filterClan = value;
UpdateFilterSummary();
RebuildCollection();
}
private void ResetFilters()
{
_filterGrade = -1;
_filterTrigger = "Все";
_filterClan = "Все";
UpdateFilterSummary();
RebuildCollection();
}
private void UpdateFilterSummary()
{
_filterSummary.text = "Фильтры: "
+ (_filterClan == "Все" ? "клан все" : "клан " + _filterClan)
+ " · " + (_filterTrigger == "Все" ? "триггер все" : _filterTrigger)
+ " · " + (_filterGrade < 0 ? "grade все" : "grade " + _filterGrade);
}
private async void SaveDeck()
{
if (_busy)
return;
_busy = true;
SetEditorStatus("Сохраняем…");
try
{
await _auth.EnsureAccessAsync();
var name = string.IsNullOrWhiteSpace(_nameInput.text)
? "Новая колода"
: _nameInput.text.Trim();
var result = _editing == null
? await _meta.CreateDeckAsync(name, new List<string>(_cards))
: await _meta.UpdateDeckAsync(_editing.Id, name, new List<string>(_cards));
_editing = result;
_cards.Clear();
_cards.AddRange(result.Cards);
_nameInput.text = result.Name;
UpsertDeck(result);
SetEditorStatus("Сохранено: " + result.Cards.Count + " карт.");
_deckCount.text = "Колода: " + _cards.Count + "/50";
RebuildEditor();
}
catch (ApiException exception)
{
var details = exception.Violations.Count > 0
? "\n" + string.Join("\n", exception.Violations.Take(3).Select(v => v.Message))
: string.Empty;
SetEditorStatus(exception.Message + details);
}
catch (Exception exception)
{
Debug.LogException(exception);
SetEditorStatus("Не удалось сохранить колоду.");
}
finally
{
_busy = false;
}
}
private void UpsertDeck(DeckDto deck)
{
var index = _decks.FindIndex(item => item.Id == deck.Id);
if (index >= 0)
_decks[index] = deck;
else
_decks.Add(deck);
}
private async void DeleteDeck(DeckDto deck)
{
if (_busy)
return;
_busy = true;
try
{
await _meta.DeleteDeckAsync(deck.Id);
_decks.RemoveAll(item => item.Id == deck.Id);
BuildDeckList();
_listStatus.text = "Колода удалена.";
}
catch (ApiException exception)
{
_listStatus.text = exception.Message;
}
catch (Exception exception)
{
Debug.LogException(exception);
_listStatus.text = "Не удалось удалить колоду.";
}
finally
{
_busy = false;
}
}
private void SetEditorStatus(string text)
{
_editorStatus.text = text;
_editorStatus.color = text.StartsWith("Не удалось", StringComparison.Ordinal)
|| text.Contains("Не больше") || text.Contains("В колоде уже") || text.Contains("больше нет")
? new Color(1f, 0.55f, 0.45f)
: new Color(0.62f, 0.68f, 0.82f);
}
private void ShowList()
{
_editorView.gameObject.SetActive(false);
_filterOverlay.gameObject.SetActive(false);
_confirmOverlay.gameObject.SetActive(false);
_listView.gameObject.SetActive(true);
}
private static void ClearChildren(Transform parent)
{
for (var i = parent.childCount - 1; i >= 0; i--)
UnityEngine.Object.Destroy(parent.GetChild(i).gameObject);
}
}
}