Files

200 lines
7.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
// Одна строка таблицы рейтинга (ссылки на её элементы; заполняет RatingPanel)
[Serializable]
public class RatingRowView
{
public GameObject root;
public Image background;
public Image medal;
public TMP_Text placeText;
public Image avatar;
public TMP_Text avatarLetter;
public TMP_Text nameText;
public Image circleBanner;
public TMP_Text circleText;
public TMP_Text powerText;
public Button inspectButton;
}
// Панель "Рейтинг": разделы слева, таблица "Место / Игрок / Круг / Мощь", страницы внизу.
public class RatingPanel : MonoBehaviour
{
public const int PageSize = 10;
[Header("Общее")]
public Button closeButton;
public TMP_Text statusText;
[Header("Разделы (порядок как в RatingCategory)")]
public Button[] categoryButtons;
[Header("Таблица")]
public TMP_Text nameColumnHeader;
public TMP_Text circleColumnHeader;
public RatingRowView[] rows;
public TMP_Text emptyText;
[Header("Страницы")]
public Button prevPageButton;
public Button nextPageButton;
public Button[] pageButtons;
static readonly Color GoldRow = new Color(0.45f, 0.32f, 0.1f, 0.95f);
static readonly Color SilverRow = new Color(0.28f, 0.31f, 0.36f, 0.95f);
static readonly Color BronzeRow = new Color(0.36f, 0.22f, 0.12f, 0.95f);
static readonly Color EvenRow = new Color(0.07f, 0.09f, 0.13f, 0.95f);
static readonly Color OddRow = new Color(0.1f, 0.12f, 0.17f, 0.95f);
static readonly Color[] MedalColors = { new Color(0.95f, 0.75f, 0.3f), new Color(0.8f, 0.83f, 0.88f), new Color(0.8f, 0.5f, 0.28f) };
static readonly Color ActiveButton = new Color(0.16f, 0.35f, 0.7f);
static readonly Color InactiveButton = new Color(0.1f, 0.12f, 0.17f);
static readonly Color[] BannerColors =
{
new Color(0.15f, 0.35f, 0.75f), new Color(0.45f, 0.2f, 0.18f), new Color(0.7f, 0.15f, 0.15f),
new Color(0.2f, 0.45f, 0.2f), new Color(0.75f, 0.5f, 0.1f), new Color(0.35f, 0.35f, 0.4f)
};
private RatingCategory category = RatingCategory.Players;
private int pageIndex;
void Awake()
{
closeButton.onClick.AddListener(Close);
prevPageButton.onClick.AddListener(() => ShowPage(pageIndex - 1));
nextPageButton.onClick.AddListener(() => ShowPage(pageIndex + 1));
for (int i = 0; i < categoryButtons.Length; i++)
{
RatingCategory c = (RatingCategory)i;
categoryButtons[i].onClick.AddListener(() => SelectCategory(c));
}
foreach (var row in rows)
{
// Круглая медаль -- спрайт генерируется в коде, поэтому ставим его здесь, а не в сборщике сцены
row.medal.sprite = PrimitiveSprites.Circle;
}
}
public void Open()
{
gameObject.SetActive(true);
SelectCategory(category);
}
public void Close()
{
gameObject.SetActive(false);
}
void SelectCategory(RatingCategory newCategory)
{
category = newCategory;
for (int i = 0; i < categoryButtons.Length; i++)
categoryButtons[i].image.color = i == (int)category ? ActiveButton : InactiveButton;
bool circles = category == RatingCategory.Circles;
nameColumnHeader.text = circles ? "Круг" : "Игрок";
circleColumnHeader.text = circles ? "Глава" : "Круг";
ShowPage(0);
}
int PageCount => Mathf.Max(1, Mathf.CeilToInt(RatingService.Instance.GetTotalCount(category) / (float)PageSize));
void ShowPage(int newPageIndex)
{
pageIndex = Mathf.Clamp(newPageIndex, 0, PageCount - 1);
statusText.text = "";
bool available = RatingService.Instance.IsAvailable(category);
emptyText.gameObject.SetActive(!available);
emptyText.text = "Раздел в разработке";
List<RatingEntry> entries = available
? RatingService.Instance.GetPage(category, pageIndex, PageSize)
: new List<RatingEntry>();
for (int i = 0; i < rows.Length; i++)
{
if (i < entries.Count) FillRow(rows[i], entries[i], i);
else rows[i].root.SetActive(false);
}
UpdatePagination(available);
}
void FillRow(RatingRowView row, RatingEntry entry, int indexOnPage)
{
row.root.SetActive(true);
bool top3 = entry.place <= 3;
row.background.color = entry.place == 1 ? GoldRow
: entry.place == 2 ? SilverRow
: entry.place == 3 ? BronzeRow
: indexOnPage % 2 == 0 ? EvenRow : OddRow;
row.medal.gameObject.SetActive(top3);
if (top3) row.medal.color = MedalColors[entry.place - 1];
row.placeText.text = entry.place.ToString();
row.placeText.color = top3 ? new Color(0.2f, 0.12f, 0.05f) : Color.white;
// Аватар-заглушка: цветной квадрат с первой буквой имени
row.avatar.color = Color.Lerp(BannerColors[StableHash(entry.name) % BannerColors.Length], Color.black, 0.25f);
row.avatarLetter.text = entry.name.Substring(0, 1).ToUpper();
row.nameText.text = entry.name;
row.circleText.text = entry.circleName;
row.circleBanner.color = BannerColors[StableHash(category == RatingCategory.Circles ? entry.name : entry.circleName) % BannerColors.Length];
row.powerText.text = FormatPower(entry.power);
string name = entry.name;
row.inspectButton.onClick.RemoveAllListeners();
row.inspectButton.onClick.AddListener(() => statusText.text = $"Профиль «{name}» пока недоступен");
}
void UpdatePagination(bool available)
{
int pageCount = PageCount;
prevPageButton.interactable = available && pageIndex > 0;
nextPageButton.interactable = available && pageIndex < pageCount - 1;
// Окно из 5 номеров вокруг текущей страницы
int first = Mathf.Clamp(pageIndex - pageButtons.Length / 2, 0, Mathf.Max(0, pageCount - pageButtons.Length));
for (int i = 0; i < pageButtons.Length; i++)
{
int page = first + i;
Button button = pageButtons[i];
bool visible = available && page < pageCount;
button.gameObject.SetActive(visible);
if (!visible) continue;
button.GetComponentInChildren<TMP_Text>().text = (page + 1).ToString();
button.image.color = page == pageIndex ? ActiveButton : InactiveButton;
button.onClick.RemoveAllListeners();
button.onClick.AddListener(() => ShowPage(page));
}
}
// 128530000 -> "128 530 000"
static string FormatPower(long power)
{
return power.ToString("N0", CultureInfo.InvariantCulture).Replace(',', ' ');
}
// string.GetHashCode в Unity может меняться между запусками -- для цвета баннера нужен стабильный
static int StableHash(string s)
{
int hash = 17;
foreach (char c in s) hash = hash * 31 + c;
return hash & int.MaxValue;
}
}