Danro/Assets/Scripts/UI/RulesPrizeListPage.cs
Danil Iarosh cb2e83a596
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
feat(ui, prizes, spawners, editor): expand prize flow and add spawning/editor tooling
- доработан flow призовой витрины и связанного UI:
  добавлены `PrizeStandButtonView` и `RulesPrizeListPage`, обновлены
  `PrizeStandController`, `PrizeStandPrize`, `PrizeStandModal`,
  `PrizeStandSettingsModal`, `LevelPrizeSettingsModal`, `QRCodeSettingsModal`,
  `BalanceDisplay`, `ButtonTextFocusColor` и связанные prefab'ы,
  чтобы улучшить настройку, навигацию, отображение кнопок и prize flow
- расширена система настроек и сохранения значений:
  `OptionPicker` получил отдельный формат `ValueSelector`,
  обновлены `SettingsDatabase`, `SettingEntryDrawer`, `SettingUIItem`,
  `SettingsUIGenerator`, `ValueSelectorRow` и `MainSettingsDatabase.asset`,
  а сохранение option picker-значений, включая `gameplay.gameDifficulty`,
  исправлено и синхронизировано с остальными настройками
- добавлены универсальные спавнеры сущностей:
  реализованы `EntitySpawner` и `EntitySpawnerManager` с поддержкой
  весов префабов, точек спавна, заполнения по проценту, ограничений
  на количество и автоспавна при инициализации
- доработана логика игрового спавна и сложности:
  процент инициализационного спавна может зависеть от настройки
  `gameplay.gameDifficulty` с градациями 25/50/75/100,
  в одном цикле массового спавна повторно не используется та же точка,
  а gizmo спавнера теперь показывает только сами точки без линий
- упрощена работа в редакторе Unity и добавлены editor-инструменты:
  в `GameManager` лицензия пропускается при запуске в `UNITY_EDITOR`,
  а `HierarchySelectionCounter`, `HierarchySelectionCountOverlay`
  и `HierarchyBulkRenamerWindow` добавляют подсчет выделения,
  realtime overlay в `SceneView` и массовое переименование объектов
  по правилам нумерации из Unity Project Settings
- синхронизированы сцены, ресурсы и UI-ассеты под новый flow:
  обновлены `MainMenu`, `Level UI`, уровни `1-1` и `1-2`,
  строки настроек, кнопки, TMP/font assets, изображения UI,
  `prizes.json` и recovery-данные после правок интерфейса и логики
- очищены и переразложены часть внешних и служебных ресурсов:
  удалён пакет `Lava_Flowing_Shader`, переименованы/заменены
  отдельные UI-prefab'ы и добавлены сопутствующие проектные артефакты,
  которые сейчас используются обновлённой конфигурацией проекта
2026-04-28 14:05:05 +03:00

196 lines
6.7 KiB
C#

using System.Collections.Generic;
using System.IO;
using System.Text;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using RR.Common.Managers;
using RR.Common.Prizes;
namespace RR.Common.UI
{
/// <summary>
/// Страница правил, которая динамически выводит призы за уровни из настроек призовой системы.
/// Повесьте компонент на GameObject страницы и назначьте TMP_Text либо оставьте поле пустым,
/// чтобы компонент нашёл первый TMP_Text в дочерних объектах.
/// </summary>
[DisallowMultipleComponent]
public sealed class RulesPrizeListPage : MonoBehaviour
{
[Header("Text")]
[SerializeField] private TMP_Text targetText;
[Header("Content")]
[SerializeField] private string title = "Призы за уровни";
[SerializeField] private string emptyPrizeText = "без приза";
[SerializeField] private string noLevelsText = "Уровни не найдены.";
[SerializeField] private string unavailableText = "Настройки призов недоступны.";
[SerializeField] private bool showTitle = true;
[SerializeField] private bool showLevelsWithoutPrize = true;
[Header("Levels")]
[SerializeField] private bool includeOnlyLevelsFolder = true;
[SerializeField] private string levelsFolderName = "Levels";
[SerializeField] private List<string> ignoredSceneNames = new() { "MainMenu", "Level UI" };
private void Awake()
{
ResolveText();
}
private void OnEnable()
{
Refresh();
}
[ContextMenu("Refresh Prize List")]
public void Refresh()
{
ResolveText();
if (targetText == null)
{
Debug.LogWarning($"[{nameof(RulesPrizeListPage)}] TMP_Text не найден на странице правил.", this);
return;
}
PrizeSystemManager manager = PrizeSystemManager.Instance;
PrizeDatabase database = manager != null ? manager.Database : null;
if (database == null)
{
targetText.text = unavailableText;
return;
}
List<string> levels = GetLevelSceneNames();
if (levels.Count == 0)
{
levels.AddRange(database.GetAll().Keys);
levels.Sort();
}
if (levels.Count == 0)
{
targetText.text = noLevelsText;
return;
}
StringBuilder builder = new();
if (showTitle && !string.IsNullOrWhiteSpace(title))
{
builder.AppendLine(title);
builder.AppendLine();
}
for (int i = 0; i < levels.Count; i++)
{
string levelName = levels[i];
LevelPrizeConfig config = database.GetConfig(levelName);
if (!showLevelsWithoutPrize && (config == null || config.type == PrizeType.None))
{
continue;
}
builder.Append(levelName);
builder.Append(" — ");
builder.Append(BuildPrizeText(manager, config));
if (i < levels.Count - 1)
{
builder.AppendLine();
}
}
string result = builder.ToString().TrimEnd();
targetText.text = string.IsNullOrWhiteSpace(result) ? noLevelsText : result;
}
private void ResolveText()
{
if (targetText == null)
{
targetText = GetComponent<TMP_Text>();
}
if (targetText == null)
{
targetText = GetComponentInChildren<TMP_Text>(true);
}
}
private List<string> GetLevelSceneNames()
{
List<string> result = new();
int sceneCount = SceneManager.sceneCountInBuildSettings;
for (int i = 0; i < sceneCount; i++)
{
string scenePath = SceneUtility.GetScenePathByBuildIndex(i);
if (string.IsNullOrWhiteSpace(scenePath))
{
continue;
}
string normalizedPath = scenePath.Replace("\\", "/");
if (includeOnlyLevelsFolder)
{
string folderToken = $"/{levelsFolderName}/";
if (!normalizedPath.Contains(folderToken))
{
continue;
}
}
string sceneName = Path.GetFileNameWithoutExtension(scenePath);
if (ignoredSceneNames != null && ignoredSceneNames.Contains(sceneName))
{
continue;
}
result.Add(sceneName);
}
return result;
}
private string BuildPrizeText(PrizeSystemManager manager, LevelPrizeConfig config)
{
if (config == null || config.type == PrizeType.None)
{
return emptyPrizeText;
}
return config.type switch
{
PrizeType.QRCode => BuildQrPrizeText(manager, config.qrTier),
PrizeType.Hopper => $"хоппер, капсул: {Mathf.Max(1, config.hopperCapsuleCount)}",
PrizeType.PrizeStand => BuildPrizeStandText(manager),
_ => config.type.ToString()
};
}
private static string BuildQrPrizeText(PrizeSystemManager manager, QRTier tier)
{
string tierText = tier switch
{
QRTier.Bronze => "бронзовый QR-код",
QRTier.Silver => "серебряный QR-код",
QRTier.Gold => "золотой QR-код",
_ => "QR-код"
};
string prizeText = manager != null ? manager.GetQRCodeModalPrizeText(tier) : string.Empty;
return string.IsNullOrWhiteSpace(prizeText)
? tierText
: $"{tierText}: {prizeText.Trim()}";
}
private static string BuildPrizeStandText(PrizeSystemManager manager)
{
PrizeStandSettings settings = manager != null ? manager.GetPrizeStandSettings() : null;
int boxCount = settings?.boxes != null ? settings.boxes.Count : 0;
return boxCount > 0 ? $"призовая витрина, ячеек: {boxCount}" : "призовая витрина";
}
}
}