Files
tcg-client/Assets/TcgEngine/Scripts/Menu/CollectionPanel.cs
2025-10-15 20:04:17 +08:00

948 lines
31 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.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace TcgEngine.UI
{
/// <summary>
/// CollectionPanel 玩家可以看到他们拥有的所有卡片的面板
/// 还有他们可以使用甲板建造者的面板
/// </summary>
public class CollectionPanel : UIPanel
{
[Header("Cards")]
public ScrollRect scroll_rect;
public RectTransform scroll_content;
public CardGrid grid_content;
public GameObject card_prefab;
[Header("Left Side")]
public IconButton[] team_filters;
public Toggle toggle_owned;
public Toggle toggle_not_owned;
public Toggle toggle_character;
public Toggle toggle_spell;
public Toggle toggle_artifact;
public Toggle toggle_equipment;
public Toggle toggle_secret;
public Toggle toggle_common;
public Toggle toggle_uncommon;
public Toggle toggle_rare;
public Toggle toggle_mythic;
public Toggle toggle_foil;
public Dropdown sort_dropdown;
public InputField search;
[Header("Right Side")]
public UIPanel deck_list_panel;
public UIPanel card_list_panel;
public DeckLine[] deck_lines;
[Header("Deckbuilding")]
public InputField deck_title;
public Text deck_quantity;
public GameObject deck_cards_prefab;
public RectTransform deck_content;
public GridLayoutGroup deck_grid;
public IconButton[] hero_powers;
[Header("切换卡组面板")]
public UIPanel changeCover;
private TeamData filter_team = null;
public int filter_dropdown = 0;
private string filter_search = "";
private List<CollectionCard> card_list = new List<CollectionCard>();
private List<CollectionCard> all_list = new List<CollectionCard>();
private List<DeckLine> deck_card_lines = new List<DeckLine>();
private string current_deck_tid;
private Image current_deck_image;
private bool editing_deck = false;
private bool saving = false;
private bool spawned = false;
private bool update_grid = false;
private float update_grid_timer = 0f;
// 分帧加载
private Coroutine spawnRoutine;
private List<UserCardData> deck_cards = new List<UserCardData>();
private static CollectionPanel instance;
protected override void Awake()
{
base.Awake();
instance = this;
//Delete grid content
for (int i = 0; i < grid_content.transform.childCount; i++)
Destroy(grid_content.transform.GetChild(i).gameObject);
for (int i = 0; i < deck_grid.transform.childCount; i++)
Destroy(deck_grid.transform.GetChild(i).gameObject);
foreach (DeckLine line in deck_lines)
line.onClick += OnClickDeckLine;
foreach (DeckLine line in deck_lines)
line.onClickDelete += OnClickDeckDelete;
foreach (DeckLine line in deck_lines)
line.onChangeCover += OnChangeCover;
foreach (IconButton button in team_filters)
button.onClick += OnClickTeam;
}
protected override void Start()
{
base.Start();
// 设置点击能力悬停文本
foreach (IconButton btn in hero_powers)
{
CardData icard = CardData.Get(btn.GetValue());
HoverTargetUI hover = btn.GetComponent<HoverTargetUI>();
AbilityData iability = icard?.GetAbility(AbilityTrigger.Activate);
if (icard != null && hover != null && iability != null)
{
string color = ColorUtility.ToHtmlStringRGBA(icard.team.color);
hover.text = "<b><color=#" + color + ">Hero Power: </color>";
hover.text += icard.title + "</b>\n " + iability.GetDesc(icard);
if (iability.mana_cost > 0)
hover.text += " <size=16>Mana: " + iability.mana_cost + "</size>";
}
}
}
protected override void Update()
{
base.Update();
}
private void LateUpdate()
{
//Resize grid
update_grid_timer += Time.deltaTime;
if (update_grid && update_grid_timer > 0.2f)
{
grid_content.GetColumnAndRow(out int rows, out int cols);
if (cols > 0)
{
float row_height = grid_content.GetGrid().cellSize.y + grid_content.GetGrid().spacing.y;
float height = rows * row_height;
scroll_content.sizeDelta = new Vector2(scroll_content.sizeDelta.x, height + 100);
update_grid = false;
}
}
}
public void SpawnCards()
{
spawned = true;
all_list = CardUILoading.Get().CardList();
foreach (CollectionCard dCard in all_list)
{
dCard.onClick += OnClickCard;
dCard.onClickRight += OnClickCardRight;
}
// if (spawnRoutine != null)
// StopCoroutine(spawnRoutine);
// spawnRoutine = StartCoroutine(SpawnCardsCoroutine());
}
private IEnumerator SpawnCardsCoroutine()
{
spawned = true;
foreach (CollectionCard card in all_list)
Destroy(card.gameObject);
all_list.Clear();
int batchSize = 2; // 每帧生成2张卡
int counter = 0;
List<VariantData> variants = VariantData.GetAll();
List<CardData> cards = CardData.GetAll();
foreach (VariantData variant in variants)
{
foreach (CardData card in cards)
{
GameObject nCard = Instantiate(card_prefab, grid_content.transform);
CollectionCard dCard = nCard.GetComponent<CollectionCard>();
dCard.SetCard(card, variant, 0);
dCard.onClick += OnClickCard;
dCard.onClickRight += OnClickCardRight;
all_list.Add(dCard);
nCard.SetActive(false);
counter++;
if (counter % batchSize == 0)
yield return null; // 每 batchSize 张卡让出一帧
}
}
}
// private IEnumerator SpawnCardsCoroutine()
// {
// spawned = true;
// foreach (CollectionCard card in all_list)
// Destroy(card.gameObject);
// all_list.Clear();
//
// int batchSize = 2; // 每帧生成2张卡
// int counter = 0;
//
// List<VariantData> variants = VariantData.GetAll();
// List<CardData> cards = CardData.GetAll();
//
// foreach (VariantData variant in variants)
// {
// foreach (CardData card in cards)
// {
// GameObject nCard = Instantiate(card_prefab, grid_content.transform);
// CollectionCard dCard = nCard.GetComponent<CollectionCard>();
// dCard.SetCard(card, variant, 0);
// dCard.onClick += OnClickCard;
// dCard.onClickRight += OnClickCardRight;
//
// all_list.Add(dCard);
// nCard.SetActive(false);
//
// counter++;
// if (counter % batchSize == 0)
// yield return null; // 每 batchSize 张卡让出一帧
// }
// }
//
// isLoaded = true;
// Hide();
// }
//----- Reload User Data ---------------
public async void ReloadUser()
{
await Authenticator.Get().LoadUserData();
MainMenu.Get().RefreshDeckList();
RefreshCardsQuantities();
if (!editing_deck)
RefreshDeckList();
}
public async void ReloadUserCards()
{
await Authenticator.Get().LoadUserData();
RefreshCardsQuantities();
}
public void RefreshCover(Image cover)
{
current_deck_image.sprite = cover.sprite;
}
public async void ReloadUserDecks()
{
await Authenticator.Get().LoadUserData();
MainMenu.Get().RefreshDeckList();
RefreshDeckList();
}
//----- Refresh UI --------
public void RefreshAll()
{
RefreshFilters();
RefreshCards();
RefreshDeckList();
RefreshStarterDeck();
}
/// <summary>
/// 筛选刷新
/// </summary>
private void RefreshFilters()
{
search.text = "";
sort_dropdown.value = 0;
foreach (IconButton button in team_filters)
button.Deactivate();
filter_team = null;
filter_dropdown = 0;
filter_search = "";
}
private void ShowDeckList()
{
deck_list_panel.Show();
changeCover.Hide();
card_list_panel.Hide();
editing_deck = false;
}
private void ShowDeckCards()
{
deck_list_panel.Hide();
changeCover.Hide();
card_list_panel.Show();
}
public void RefreshCards()
{
if (!spawned)
SpawnCards();
card_list.Clear();
UserData udata = Authenticator.Get().UserData;
if (udata == null)
return;
VariantData variant = VariantData.GetDefault();
VariantData special = VariantData.GetSpecial();
if (toggle_foil.isOn && special != null)
variant = special;
List<CardDataQ> all_cards = new List<CardDataQ>();
List<CardDataQ> shown_cards = new List<CardDataQ>();
foreach (CardData icard in CardData.GetAll())
{
CardDataQ card = new CardDataQ();
card.card = icard;
card.variant = variant;
card.quantity = udata.GetCardQuantity(icard, variant);
all_cards.Add(card);
}
// 统一的基础排序逻辑
all_cards.Sort((CardDataQ a, CardDataQ b) =>
{
// 第一级排序isOnlinefalse 的永远在最下面)
bool aOnline = a.card.isOnline;
bool bOnline = b.card.isOnline;
if (aOnline != bOnline)
return bOnline.CompareTo(aOnline); // true在前 false在后
// 第二级排序:根据 dropdown 决定方式
switch (filter_dropdown)
{
case 0: // Name
return a.card.title.CompareTo(b.card.title);
case 1: // Attack
return b.card.attack == a.card.attack ? b.card.hp.CompareTo(a.card.hp) : b.card.attack.CompareTo(a.card.attack);
case 2: // HP
return b.card.hp == a.card.hp ? b.card.attack.CompareTo(a.card.attack) : b.card.hp.CompareTo(a.card.hp);
case 3: // Mana Cost
return b.card.mana == a.card.mana ? a.card.title.CompareTo(b.card.title) : a.card.mana.CompareTo(b.card.mana);
default:
return a.card.title.CompareTo(b.card.title);
}
});
foreach (CardDataQ card in all_cards)
{
if (card.card.deckbuilding)
{
CardData icard = card.card;
if (filter_team == null || filter_team == icard.team)
{
bool owned = card.quantity > 0;
RarityData rarity = icard.rarity;
CardType type = icard.type;
bool owned_check = (owned && toggle_owned.isOn)
|| (!owned && toggle_not_owned.isOn)
|| toggle_owned.isOn == toggle_not_owned.isOn;
bool type_check = (type == CardType.Character && toggle_character.isOn)
|| (type == CardType.Spell && toggle_spell.isOn)
|| (type == CardType.Artifact && toggle_artifact.isOn)
|| (type == CardType.Equipment && toggle_equipment.isOn)
|| (type == CardType.Secret && toggle_secret.isOn)
|| (!toggle_character.isOn && !toggle_spell.isOn && !toggle_artifact.isOn && !toggle_equipment.isOn && !toggle_secret.isOn);
bool rarity_check = (rarity.rank == 1 && toggle_common.isOn)
|| (rarity.rank == 2 && toggle_uncommon.isOn)
|| (rarity.rank == 3 && toggle_rare.isOn)
|| (rarity.rank == 4 && toggle_mythic.isOn)
|| (!toggle_common.isOn && !toggle_uncommon.isOn && !toggle_rare.isOn && !toggle_mythic.isOn);
string search = filter_search.ToLower();
bool search_check = string.IsNullOrWhiteSpace(search)
|| icard.id.Contains(search)
|| icard.title.ToLower().Contains(search)
|| icard.GetText().ToLower().Contains(search);
if (owned_check && type_check && rarity_check && search_check)
{
shown_cards.Add(card);
}
}
}
}
int index = 0;
foreach (CardDataQ qcard in shown_cards)
{
if (index < all_list.Count)
{
CollectionCard dcard = all_list[index];
dcard.SetCard(qcard.card, qcard.variant, 0);
card_list.Add(dcard);
if (!dcard.gameObject.activeSelf)
dcard.gameObject.SetActive(true);
index++;
}
}
for (int i = index; i < all_list.Count; i++)
all_list[i].gameObject.SetActive(false);
update_grid = true;
update_grid_timer = 0f;
scroll_rect.verticalNormalizedPosition = 1f;
RefreshCardsQuantities();
}
private void RefreshCardsQuantities()
{
UserData udata = Authenticator.Get().UserData;
foreach (CollectionCard card in card_list)
{
CardData icard = card.GetCard();
VariantData ivariant = card.GetVariant();
bool owned = IsCardOwned(udata, icard, ivariant, 1);
int quantity = udata.GetCardQuantity(icard, ivariant);
card.SetQuantity(quantity);
card.SetGrayscale(!owned);
}
}
private void RefreshDeckList()
{
foreach (DeckLine line in deck_lines)
line.Hide();
deck_cards.Clear();
editing_deck = false;
saving = false;
UserData udata = Authenticator.Get().UserData;
if (udata == null)
return;
int index = 0;
foreach (UserDeckData deck in udata.decks)
{
if (index < deck_lines.Length)
{
DeckLine line = deck_lines[index];
line.SetLine(udata, deck);
}
index++;
}
if (index < deck_lines.Length)
{
DeckLine line = deck_lines[index];
line.SetLine("+");
}
RefreshCardsQuantities();
}
public void RefreshDeck(UserDeckData deck)
{
deck_title.text = "Deck Name";
current_deck_tid = GameTool.GenerateRandomID(7);
deck_cards.Clear();
saving = false;
editing_deck = true;
foreach (IconButton btn in hero_powers)
btn.Deactivate();
if (deck != null)
{
deck_title.text = deck.title;
current_deck_tid = deck.tid;
foreach (IconButton btn in hero_powers)
{
if (deck.hero != null && btn.GetValue() == deck.hero.tid)
btn.Activate();
}
for (int i = 0; i < deck.cards.Length; i++)
{
CardData card = CardData.Get(deck.cards[i].tid);
VariantData variant = VariantData.Get(deck.cards[i].variant);
if (card != null && variant != null)
{
AddDeckCard(card, variant, deck.cards[i].quantity);
}
}
}
RefreshDeckCards();
}
/// <summary>
/// 刷新牌组卡片
/// </summary>
private void RefreshDeckCards()
{
foreach (DeckLine line in deck_card_lines)
line.Hide();
List<CardDataQ> list = new List<CardDataQ>();
foreach (UserCardData card in deck_cards)
{
CardDataQ acard = new CardDataQ();
acard.card = CardData.Get(card.tid);
acard.variant = VariantData.Get(card.variant);
acard.quantity = card.quantity;
list.Add(acard);
}
list.Sort((CardDataQ a, CardDataQ b) => { return a.card.title.CompareTo(b.card.title); });
UserData udata = Authenticator.Get().UserData;
int index = 0;
int count = 0;
foreach (CardDataQ card in list)
{
if (index >= deck_card_lines.Count)
CreateDeckCard();
if (index < deck_card_lines.Count)
{
DeckLine line = deck_card_lines[index];
if (line != null)
{
line.SetLine(card.card, card.variant, card.quantity, !IsCardOwned(udata, card.card, card.variant, card.quantity));
count += card.quantity;
}
}
index++;
}
deck_quantity.text = count + "/" + GameplayData.Get().deck_size;
deck_quantity.color = count >= GameplayData.Get().deck_size ? Color.white : Color.red;
RefreshCardsQuantities();
}
// 刷新新手卡组
private void RefreshStarterDeck()
{
UserData udata = Authenticator.Get().UserData;
if (udata != null && (udata.cards.Length == 0 || udata.rewards.Length == 0))
{
if (GameplayData.Get().starter_decks.Length > 0)
{
StarterDeckPanel.Get().Show();
}
}
}
//-------- Deck editing actions
private void CreateDeckCard()
{
GameObject deck_line = Instantiate(deck_cards_prefab, deck_grid.transform);
DeckLine line = deck_line.GetComponent<DeckLine>();
deck_card_lines.Add(line);
float height = deck_card_lines.Count * 70f + 20f;
deck_content.sizeDelta = new Vector2(deck_content.sizeDelta.x, height);
line.onClick += OnClickCardLine;
line.onClickRight += OnRightClickCardLine;
}
private void AddDeckCard(CardData card, VariantData variant, int quantity = 1)
{
AddDeckCard(card.id, variant.id, quantity);
}
private void RemoveDeckCard(CardData card, VariantData variant)
{
RemoveDeckCard(card.id, variant.id);
}
private void AddDeckCard(string tid, string variant, int quantity = 1)
{
UserCardData ucard = GetDeckCard(tid, variant);
if (ucard != null)
{
ucard.quantity += quantity;
}
else
{
ucard = new UserCardData(tid, variant);
ucard.quantity = quantity;
deck_cards.Add(ucard);
}
}
private void RemoveDeckCard(string tid, string variant)
{
for (int i = deck_cards.Count - 1; i >= 0; i--)
{
UserCardData ucard = deck_cards[i];
if (ucard.tid == tid && ucard.variant == variant)
{
ucard.quantity--;
if(ucard.quantity <= 0)
deck_cards.RemoveAt(i);
}
}
}
private UserCardData GetDeckCard(string tid, string variant)
{
foreach (UserCardData ucard in deck_cards)
{
if (ucard.tid == tid && ucard.variant == variant)
return ucard;
}
return null;
}
// 保存卡组
private void SaveDeck()
{
int totalCardCount = 0;
foreach (UserCardData card in deck_cards)
{
totalCardCount += card.quantity;
}
if (totalCardCount == 0)
{
ShowNotification("卡组为空,无法保存");
return;
}
if (totalCardCount != GameplayData.Get().deck_size)
{
ShowNotification("卡组数量错误,无法保存。当前总数: " + totalCardCount + ", 需要: " + GameplayData.Get().deck_size);
return;
}
UserData udata = Authenticator.Get().UserData;
UserDeckData udeck = new UserDeckData();
udeck.tid = current_deck_tid;
udeck.title = deck_title.text;
udeck.hero = new UserCardData();
udeck.hero.tid = GetSelectedHeroId();
udeck.hero.variant = VariantData.GetDefault().id;
udeck.cards = deck_cards.ToArray();
if (string.IsNullOrEmpty(udeck.cover))
{
udeck.cover = "wind";
}
saving = true;
if (Authenticator.Get().IsTest())
SaveDeckTest(udata, udeck);
if (Authenticator.Get().IsApi())
SaveDeckAPI(udata, udeck);
ShowDeckList();
}
private async void SaveDeckTest(UserData udata, UserDeckData udeck)
{
udata.SetDeck(udeck);
await Authenticator.Get().SaveUserData();
ReloadUserDecks();
}
public async void SaveDeckAPI(UserData udata, UserDeckData udeck)
{
string url = ApiClient.ServerURL + "/users/deck/" + udeck.tid;
string jdata = ApiTool.ToJson(udeck);
WebResponse res = await ApiClient.Get().SendPostRequest(url, jdata);
UserDeckData[] decks = ApiTool.JsonToArray<UserDeckData>(res.data);
saving = res.success;
if (res.success && decks != null)
{
udata.decks = decks;
await Authenticator.Get().SaveUserData();
ReloadUserDecks();
}
}
public async void DeleteDeck(string deck_tid)
{
UserData udata = Authenticator.Get().UserData;
UserDeckData udeck = udata.GetDeck(deck_tid);
List<UserDeckData> decks = new List<UserDeckData>(udata.decks);
decks.Remove(udeck);
udata.decks = decks.ToArray();
if (Authenticator.Get().IsApi())
{
string url = ApiClient.ServerURL + "/users/deck/" + deck_tid;
await ApiClient.Get().SendRequest(url, "DELETE", "");
}
await Authenticator.Get().SaveUserData();
ReloadUserDecks();
}
//---- Left Panel Filters Clicks -----------
public void OnClickTeam(IconButton button)
{
filter_team = null;
if (button.IsActive())
{
foreach (TeamData team in TeamData.GetAll())
{
if (button.GetValue() == TeamTitleData.Team(team.id))
filter_team = team;
}
}
RefreshCards();
}
public void OnChangeToggle()
{
RefreshCards();
}
public void OnChangeDropdown()
{
filter_dropdown = sort_dropdown.value;
RefreshCards();
}
public void OnChangeSearch()
{
filter_search = search.text;
RefreshCards();
}
//---- Card grid clicks ----------
public void OnClickCard(CardUI card)
{
if (!editing_deck)
{
CardZoomPanel.Get().ShowCard(card.GetCard(), card.GetVariant());
return;
}
CardData icard = card.GetCard();
VariantData variant = card.GetVariant();
if (icard != null)
{
int in_deck = CountDeckCards(icard, variant);
int in_deck_same = CountDeckCards(icard);
UserData udata = Authenticator.Get().UserData;
bool owner = IsCardOwned(udata, card.GetCard(), card.GetVariant(), in_deck + 1);
bool deck_limit = in_deck_same < GameplayData.Get().deck_duplicate_max;
bool ssr_limit = CheckSSRLimit(icard);
if (owner && deck_limit && ssr_limit)
{
AddDeckCard(icard, variant);
RefreshDeckCards();
}
else
{
if (!owner)
ShowNotification("卡牌数量不足");
else if (!deck_limit)
ShowNotification("相同卡牌最多只能添加 " + GameplayData.Get().deck_duplicate_max + " 张");
else if (!ssr_limit)
ShowNotification("SSR以上卡牌最多只能添加 " + GameplayData.Get().deck_ssr_max + " 张");
}
}
}
public void OnClickCardRight(CardUI card)
{
CardZoomPanel.Get().ShowCard(card.GetCard(), card.GetVariant());
}
//---- Right Panel Click -------
public void OnClickDeckLine(DeckLine line)
{
if (line.IsHidden() || saving)
return;
UserDeckData deck = line.GetUserDeck();
RefreshDeck(deck);
ShowDeckCards();
}
private void OnClickCardLine(DeckLine line)
{
CardData card = line.GetCard();
VariantData variant = line.GetVariant();
if (card != null)
{
RemoveDeckCard(card, variant);
}
RefreshDeckCards();
}
private void OnRightClickCardLine(DeckLine line)
{
CardData icard = line.GetCard();
if (icard != null)
CardZoomPanel.Get().ShowCard(icard, line.GetVariant());
}
// ---- Deck editing Click -----
public void OnClickSaveDeck()
{
if (!saving)
{
SaveDeck();
}
}
public void OnClickDeckBack()
{
ShowDeckList();
}
public void OnClickDeleteDeck()
{
if (editing_deck && !string.IsNullOrEmpty(current_deck_tid))
{
DeleteDeck(current_deck_tid);
}
}
public void OnClickDeckDelete(DeckLine line)
{
if (line.IsHidden())
return;
UserDeckData deck = line.GetUserDeck();
if (deck != null)
{
DeleteDeck(deck.tid);
}
}
public void OnChangeCover(DeckLine line)
{
current_deck_image = line.image;
ChangeCoverPanel.Get().OpenPanel(line);
}
// ---- Getters -----
public int CountDeckCards(CardData card, VariantData cvariant)
{
int count = 0;
foreach (UserCardData ucard in deck_cards)
{
if (ucard.tid == card.id && ucard.variant == cvariant.id)
count += ucard.quantity;
}
return count;
}
public int CountDeckCards(CardData card)
{
int count = 0;
foreach (UserCardData ucard in deck_cards)
{
if (ucard.tid == card.id)
count += ucard.quantity;
}
return count;
}
private bool IsCardOwned(UserData udata, CardData card, VariantData variant, int quantity)
{
return udata.GetCardQuantity(card, variant) >= quantity;
}
private bool CheckSSRLimit(CardData card)
{
// 检查卡牌稀有度是否 >= 4 (SSR级别)
if (card.rarity.rank >= 4)
{
int ssr_count = CountSSRCards();
return ssr_count < GameplayData.Get().deck_ssr_max;
}
return true;
}
private int CountSSRCards()
{
int count = 0;
foreach (UserCardData ucard in deck_cards)
{
CardData card = CardData.Get(ucard.tid);
if (card != null && card.rarity.rank >= 4)
{
count += ucard.quantity;
}
}
return count;
}
private string GetSelectedHeroId()
{
foreach (IconButton btn in hero_powers)
{
if (btn.IsActive())
return btn.GetValue();
}
return "";
}
private void ShowNotification(string message)
{
WarningText.ShowText(message);
}
public override void Show(bool instant = false)
{
base.Show(instant);
RefreshAll();
ShowDeckList();
}
public override void Hide(bool instant = false)
{
base.Hide(instant);
}
public static CollectionPanel Get()
{
return instance;
}
}
public struct CardDataQ
{
public CardData card;
public VariantData variant;
public int quantity;
}
}