Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- доработан 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'ы и добавлены сопутствующие проектные артефакты, которые сейчас используются обновлённой конфигурацией проекта
194 lines
6.9 KiB
C#
194 lines
6.9 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|