Danro/Assets/Scripts/Prizes/PrizeStandPrize.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

194 lines
6.9 KiB
C#
Raw Permalink 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 UnityEngine;
using RR.Common.Controls;
using RR.Common.Managers;
using RR.Common.UI;
using RR.Game2D.Behaviour;
using RR.Game2D.Core.Managers;
using ControlsBoxStatus = RR.Common.Controls.BoxStatus;
namespace RR.Common.Prizes
{
/// <summary>
/// Исполнитель приза «Витрина».
/// Показывает модальное окно с выбором ячейки, затем открывает её
/// через <see cref="PrizeStandController"/>. Количество ячеек берётся из ComSystemManager.
/// </summary>
public class PrizeStandPrize : PrizeBase
{
public override PrizeType Type => PrizeType.PrizeStand;
public override bool IsGiving => _isGiving;
private readonly Func<PrizeStandModal> _resolveModal;
private bool _isGiving;
private PrizeStandModal _activeModal;
/// <param name="resolveModal">Делегат для поиска модального окна в текущих сценах.</param>
public PrizeStandPrize(Func<PrizeStandModal> resolveModal)
{
_resolveModal = resolveModal;
}
public override void Execute(MonoBehaviour host, LevelPrizeConfig config)
{
if (_isGiving)
{
Debug.LogWarning("[PrizeStandPrize] Уже идёт выдача.");
return;
}
_isGiving = true;
ShowPrizeStandSelectionAsync();
}
private async void ShowPrizeStandSelectionAsync()
{
try
{
// Отписываемся от предыдущего модального окна, если было
if (_activeModal != null)
_activeModal.OnBoxSelected -= OnBoxSelected;
_activeModal = _resolveModal?.Invoke();
if (_activeModal == null)
{
Debug.LogError("[PrizeStandPrize] PrizeStandModal не найден на сцене.");
_isGiving = false;
InvokeResult(false);
return;
}
_activeModal.OnBoxSelected += OnBoxSelected;
PrizeStandController stand = ComSystemManager.GetPrizeStandController();
PrizeStandConnectionInfo connectionInfo = PrizeSystemManager.Instance != null
? PrizeSystemManager.Instance.GetPrizeStandConnectionInfo()
: new PrizeStandConnectionInfo(stand != null, stand != null ? stand.GetBoxesCount() : 0);
float gameTime = GameManager.Instance != null ? GameManager.Instance.GetPendingOrCurrentWinGameTime() : 0f;
int finalScore = ScoreSystem.Instance != null
? ScoreSystem.Instance.GetDisplayScore(gameTime, true)
: 0;
PrizeStandSettings settings = PrizeSystemManager.Instance != null
? PrizeSystemManager.Instance.GetPrizeStandSettings()
: new PrizeStandSettings();
Dictionary<int, ControlsBoxStatus> boxStatuses = new();
if (stand != null && connectionInfo.IsConnected)
{
boxStatuses = await stand.GetStatusesAsync();
}
_activeModal.Show(
BuildAvailability(settings, connectionInfo, finalScore, boxStatuses),
connectionInfo.IsConnected,
connectionInfo.ConnectedBoxesCount,
gameTime,
finalScore,
true);
}
catch (Exception ex)
{
Debug.LogError($"[PrizeStandPrize] Не удалось подготовить витрину: {ex.Message}");
if (_activeModal != null)
{
_activeModal.OnBoxSelected -= OnBoxSelected;
_activeModal = null;
}
_isGiving = false;
InvokeResult(false);
}
}
private async void OnBoxSelected(int boxNumber)
{
// Отписываемся сразу после выбора
if (_activeModal != null)
{
_activeModal.OnBoxSelected -= OnBoxSelected;
_activeModal = null;
}
if (boxNumber <= 0)
{
_isGiving = false;
InvokeResult(false);
return;
}
PrizeStandController stand = ComSystemManager.GetPrizeStandController();
if (stand == null)
{
Debug.LogError("[PrizeStandPrize] Витрина не подключена.");
_isGiving = false;
InvokeResult(false);
return;
}
bool success = false;
void OnResult(bool s, string m)
{
stand.OpeningFinished -= OnResult;
success = s;
if (!s) Debug.LogWarning($"[PrizeStandPrize] Ошибка: {m}");
}
stand.OpeningFinished += OnResult;
try
{
await stand.OpenBoxAsync(boxNumber);
}
catch (Exception ex)
{
Debug.LogError($"[PrizeStandPrize] Исключение: {ex.Message}");
stand.OpeningFinished -= OnResult;
}
_isGiving = false;
InvokeResult(success);
}
private static IReadOnlyList<PrizeStandBoxAvailability> BuildAvailability(
PrizeStandSettings settings,
PrizeStandConnectionInfo connectionInfo,
int finalScore,
IReadOnlyDictionary<int, ControlsBoxStatus> boxStatuses)
{
List<PrizeStandBoxAvailability> result = new();
if (settings?.boxes == null)
return result;
foreach (PrizeStandBoxConfig box in settings.boxes)
{
if (box == null || box.boxNumber <= 0)
continue;
bool isConnectedBox = connectionInfo.IsConnected
&& (connectionInfo.ConnectedBoxesCount <= 0 || box.boxNumber <= connectionInfo.ConnectedBoxesCount);
bool isUnlocked = finalScore >= box.requiredScore;
ControlsBoxStatus status = ControlsBoxStatus.NotFound;
if (boxStatuses != null && boxStatuses.TryGetValue(box.boxNumber, out ControlsBoxStatus foundStatus))
{
status = foundStatus;
}
result.Add(new PrizeStandBoxAvailability(
box.boxNumber,
box.requiredScore,
isUnlocked,
isConnectedBox,
status));
}
return result;
}
}
}