using System; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.UI; using RR.Common.Managers; using RR.Common.Prizes; namespace RR.Common.UI { /// /// Модальное окно настройки приза за конкретный уровень. /// Для QR настраивает ранг на уровне, для витрины редактирует общую игровую конфигурацию. /// public class LevelPrizeSettingsModal : Modal { [Header("UI — Общее")] [SerializeField] private TMP_Text titleText; [SerializeField] private TMP_Text prizeTypeLabel; [SerializeField] private TMP_Text detailsText; [Header("UI — QR")] [SerializeField] private GameObject qrParamsGroup; [SerializeField] private TMP_Text qrTierLabel; [Header("UI — Параметры хоппера")] [SerializeField] private GameObject hopperParamsGroup; [SerializeField] private TMP_Text hopperCapsuleCountLabel; [Header("UI — Параметры витрины")] [SerializeField] private GameObject prizeStandParamsGroup; [SerializeField] private TMP_Text prizeStandStatusLabel; [SerializeField] private TMP_Text prizeStandBoxCountLabel; [SerializeField] private TMP_Text prizeStandSelectedBoxLabel; [SerializeField] private TMP_Text prizeStandThresholdLabel; [Header("Строки (RectTransform)")] [SerializeField] private RectTransform prizeTypeRow; [SerializeField] private RectTransform qrTierRow; [SerializeField] private RectTransform hopperParamRow; [SerializeField] private RectTransform prizeStandBoxCountRow; [SerializeField] private RectTransform prizeStandSelectedBoxRow; [SerializeField] private RectTransform prizeStandThresholdRow; [Header("Указатель")] [SerializeField] private UIRowPointer rowPointer; [Header("Кнопка закрытия")] [SerializeField] private Button closeModalButton; [Header("Навигация (Input System)")] [SerializeField] private InputActionReference navigateAction; [SerializeField] private float deadZone = 0.5f; [Header("Лимиты")] [SerializeField] private int maxCapsules = 10; [SerializeField] private int maxBoxes = 48; [SerializeField] private int defaultPrizeStandBoxesCount = 5; [SerializeField] private int scoreStep = 50; private const int ROW_TYPE = 0; private const int ROW_QR_TIER = 1; private const int ROW_HOPPER_PARAM = 2; private const int ROW_STAND_BOX_COUNT = 3; private const int ROW_STAND_SELECTED_BOX = 4; private const int ROW_STAND_THRESHOLD = 5; private const int ROW_CLOSE = 6; private const int ROW_COUNT = 7; private static readonly PrizeType[] PrizeTypes = (PrizeType[])Enum.GetValues(typeof(PrizeType)); private static readonly QRTier[] QRTiers = { QRTier.Bronze, QRTier.Silver, QRTier.Gold }; private string _levelName; private LevelPrizeConfig _config; private PrizeStandSettings _prizeStandSettings; private int _currentTypeIndex; private int _currentTierIndex; private int _activeRow; private int _selectedPrizeStandBoxIndex; private bool _axisHeldX; private bool _axisHeldY; public event Action OnApplied; protected override void OnEnable() { base.OnEnable(); if (navigateAction != null && navigateAction.action != null) { navigateAction.action.Enable(); navigateAction.action.performed += OnNavigatePerformed; navigateAction.action.canceled += OnNavigateCanceled; } } protected override void OnDisable() { if (navigateAction != null && navigateAction.action != null) { navigateAction.action.performed -= OnNavigatePerformed; navigateAction.action.canceled -= OnNavigateCanceled; } _axisHeldX = false; _axisHeldY = false; base.OnDisable(); } protected override void OnReturnedToTop() { _axisHeldX = false; _axisHeldY = false; RefreshRowHighlights(); } private void OnNavigatePerformed(InputAction.CallbackContext ctx) { if (!_isOpen) return; Vector2 v = ctx.ReadValue(); if (Mathf.Abs(v.y) >= deadZone) { if (!_axisHeldY) { _axisHeldY = true; MoveRow(v.y > 0f ? -1 : 1); } } else { _axisHeldY = false; } if (Mathf.Abs(v.x) >= deadZone) { if (!_axisHeldX) { _axisHeldX = true; HandleLeftRight(v.x > 0f ? 1 : -1); } } else { _axisHeldX = false; } } private void OnNavigateCanceled(InputAction.CallbackContext ctx) { _axisHeldX = false; _axisHeldY = false; } private void MoveRow(int delta) { int next = _activeRow; for (int i = 0; i < ROW_COUNT; i++) { next = (next + delta + ROW_COUNT) % ROW_COUNT; if (IsRowVisible(next)) { _activeRow = next; break; } } RefreshRowHighlights(); } private bool IsRowVisible(int row) { return row switch { ROW_QR_TIER => _config.type == PrizeType.QRCode, ROW_HOPPER_PARAM => _config.type == PrizeType.Hopper, ROW_STAND_BOX_COUNT => _config.type == PrizeType.PrizeStand, ROW_STAND_SELECTED_BOX => _config.type == PrizeType.PrizeStand && GetPrizeStandBoxes().Count > 0, ROW_STAND_THRESHOLD => _config.type == PrizeType.PrizeStand && GetPrizeStandBoxes().Count > 0, _ => true }; } private void HandleLeftRight(int direction) { switch (_activeRow) { case ROW_TYPE: CyclePrizeType(direction); break; case ROW_QR_TIER: AdjustQrTier(direction); break; case ROW_HOPPER_PARAM: AdjustCapsules(direction); break; case ROW_STAND_BOX_COUNT: AdjustPrizeStandBoxCount(direction); break; case ROW_STAND_SELECTED_BOX: AdjustSelectedPrizeStandBox(direction); break; case ROW_STAND_THRESHOLD: AdjustPrizeStandThreshold(direction); break; } } public void OpenForLevel(string levelName) { _levelName = levelName; PrizeDatabase db = PrizeSystemManager.Instance?.Database; _config = db != null ? CopyConfig(db.GetConfig(levelName)) : new LevelPrizeConfig(); _prizeStandSettings = db != null ? CopyPrizeStandSettings(db.GetPrizeStandSettings()) : CreateDefaultPrizeStandSettings(); EnsurePrizeStandSettingsInitialized(); _currentTypeIndex = Array.IndexOf(PrizeTypes, _config.type); if (_currentTypeIndex < 0) _currentTypeIndex = 0; _currentTierIndex = GetTierIndex(_config.qrTier); _selectedPrizeStandBoxIndex = Mathf.Clamp(_selectedPrizeStandBoxIndex, 0, Mathf.Max(0, GetPrizeStandBoxes().Count - 1)); _activeRow = ROW_TYPE; AutoResolveRows(); RefreshUI(); RefreshRowHighlights(); Open(); } public override void Close() { Save(); base.Close(); } private void CyclePrizeType(int direction) { _currentTypeIndex = (_currentTypeIndex + direction + PrizeTypes.Length) % PrizeTypes.Length; _config.type = PrizeTypes[_currentTypeIndex]; if (_config.type == PrizeType.QRCode && _config.qrTier == QRTier.None) _config.qrTier = QRTiers[0]; EnsurePrizeStandSettingsInitialized(); if (!IsRowVisible(_activeRow)) _activeRow = ROW_TYPE; RefreshUI(); RefreshRowHighlights(); } private void AdjustQrTier(int direction) { _currentTierIndex = (_currentTierIndex + direction + QRTiers.Length) % QRTiers.Length; _config.qrTier = QRTiers[_currentTierIndex]; RefreshUI(); } private void AdjustCapsules(int delta) { _config.hopperCapsuleCount = Mathf.Clamp(_config.hopperCapsuleCount + delta, 1, maxCapsules); RefreshUI(); } private void AdjustPrizeStandBoxCount(int delta) { List boxes = GetPrizeStandBoxes(); int currentCount = boxes.Count; int nextCount = Mathf.Clamp(currentCount + delta, 1, maxBoxes); if (nextCount == currentCount) return; if (nextCount > currentCount) { for (int i = currentCount + 1; i <= nextCount; i++) boxes.Add(new PrizeStandBoxConfig { boxNumber = i, requiredScore = 0 }); } else { boxes.RemoveRange(nextCount, currentCount - nextCount); } NormalizePrizeStandBoxes(); _selectedPrizeStandBoxIndex = Mathf.Clamp(_selectedPrizeStandBoxIndex, 0, boxes.Count - 1); RefreshUI(); RefreshRowHighlights(); } private void AdjustSelectedPrizeStandBox(int delta) { List boxes = GetPrizeStandBoxes(); if (boxes.Count == 0) return; _selectedPrizeStandBoxIndex = (_selectedPrizeStandBoxIndex + delta + boxes.Count) % boxes.Count; RefreshUI(); } private void AdjustPrizeStandThreshold(int direction) { PrizeStandBoxConfig selectedBox = GetSelectedPrizeStandBox(); if (selectedBox == null) return; selectedBox.requiredScore = Mathf.Max(0, selectedBox.requiredScore + direction * scoreStep); RefreshUI(); } private void Save() { if (string.IsNullOrEmpty(_levelName) || _config == null) return; PrizeDatabase db = PrizeSystemManager.Instance?.Database; PrizeSystemManager manager = PrizeSystemManager.Instance; if (db == null || manager == null) { Debug.LogError("[LevelPrizeSettingsModal] PrizeDatabase недоступна."); return; } db.SetConfig(_levelName, _config); manager.SetPrizeStandSettings(_prizeStandSettings); Debug.Log($"[LevelPrizeSettingsModal] Уровень «{_levelName}» → приз «{_config.type}» сохранён."); OnApplied?.Invoke(_levelName); } private void RefreshUI() { EnsurePrizeStandSettingsInitialized(); if (titleText) titleText.text = $"Приз за уровень {_levelName}"; if (prizeTypeLabel) prizeTypeLabel.text = GetPrizeTypeName(_config.type); if (qrParamsGroup) qrParamsGroup.SetActive(_config.type == PrizeType.QRCode); if (hopperParamsGroup) hopperParamsGroup.SetActive(_config.type == PrizeType.Hopper); if (prizeStandParamsGroup) prizeStandParamsGroup.SetActive(_config.type == PrizeType.PrizeStand); if (qrTierLabel) qrTierLabel.text = GetTierName(_config.qrTier); if (hopperCapsuleCountLabel) hopperCapsuleCountLabel.text = _config.hopperCapsuleCount.ToString(); PrizeStandConnectionInfo connectionInfo = PrizeSystemManager.Instance != null ? PrizeSystemManager.Instance.GetPrizeStandConnectionInfo() : new PrizeStandConnectionInfo(false, 0); List boxes = GetPrizeStandBoxes(); PrizeStandBoxConfig selectedBox = GetSelectedPrizeStandBox(); if (prizeStandStatusLabel) prizeStandStatusLabel.text = connectionInfo.IsConnected ? $"Подключена ({connectionInfo.ConnectedBoxesCount} ячеек)" : "Не подключена"; if (prizeStandBoxCountLabel) prizeStandBoxCountLabel.text = boxes.Count.ToString(); if (prizeStandSelectedBoxLabel) prizeStandSelectedBoxLabel.text = selectedBox != null ? selectedBox.boxNumber.ToString() : "-"; if (prizeStandThresholdLabel) prizeStandThresholdLabel.text = selectedBox != null ? selectedBox.requiredScore.ToString() : "-"; if (detailsText) detailsText.text = BuildDetailsText(connectionInfo, boxes, selectedBox); } private string BuildDetailsText(PrizeStandConnectionInfo connectionInfo, List boxes, PrizeStandBoxConfig selectedBox) { return _config.type switch { PrizeType.QRCode => $"Ранг QR-приза: {GetTierName(_config.qrTier)}", PrizeType.Hopper => $"Капсул за выдачу: {_config.hopperCapsuleCount}", PrizeType.PrizeStand => BuildPrizeStandDetails(connectionInfo, boxes, selectedBox), _ => "Для уровня приз не назначен." }; } private string BuildPrizeStandDetails(PrizeStandConnectionInfo connectionInfo, List boxes, PrizeStandBoxConfig selectedBox) { string connectionText = connectionInfo.IsConnected ? $"Витрина подключена, доступно на устройстве: {connectionInfo.ConnectedBoxesCount} ячеек." : "Витрина не подключена. Редактируется сохранённая конфигурация."; string selectedText = selectedBox != null ? $"Выбрана ячейка {selectedBox.boxNumber}, порог: {selectedBox.requiredScore} очков." : "Ячейки не настроены."; return $"{connectionText}\nСконфигурировано ячеек: {boxes.Count}\n{selectedText}"; } private void RefreshRowHighlights() { if (_activeRow == ROW_CLOSE) { if (rowPointer) rowPointer.Hide(); if (closeModalButton && EventSystem.current) EventSystem.current.SetSelectedGameObject(closeModalButton.gameObject); } else { if (rowPointer) { RectTransform target = GetActiveRowTransform(); if (target) rowPointer.SetTarget(target); } } } private RectTransform GetActiveRowTransform() { return _activeRow switch { ROW_TYPE => prizeTypeRow, ROW_QR_TIER => qrTierRow != null ? qrTierRow : prizeTypeRow, ROW_HOPPER_PARAM => hopperParamRow != null ? hopperParamRow : prizeTypeRow, ROW_STAND_BOX_COUNT => prizeStandBoxCountRow != null ? prizeStandBoxCountRow : prizeTypeRow, ROW_STAND_SELECTED_BOX => prizeStandSelectedBoxRow != null ? prizeStandSelectedBoxRow : prizeTypeRow, ROW_STAND_THRESHOLD => prizeStandThresholdRow != null ? prizeStandThresholdRow : prizeTypeRow, _ => prizeTypeRow }; } private void AutoResolveRows() { if (prizeTypeRow == null && prizeTypeLabel != null) prizeTypeRow = prizeTypeLabel.transform.parent as RectTransform; if (qrTierRow == null && qrTierLabel != null) qrTierRow = qrTierLabel.transform.parent as RectTransform; if (hopperParamRow == null && hopperCapsuleCountLabel != null) hopperParamRow = hopperCapsuleCountLabel.transform.parent as RectTransform; if (prizeStandBoxCountRow == null && prizeStandBoxCountLabel != null) prizeStandBoxCountRow = prizeStandBoxCountLabel.transform.parent as RectTransform; if (prizeStandSelectedBoxRow == null && prizeStandSelectedBoxLabel != null) prizeStandSelectedBoxRow = prizeStandSelectedBoxLabel.transform.parent as RectTransform; if (prizeStandThresholdRow == null && prizeStandThresholdLabel != null) prizeStandThresholdRow = prizeStandThresholdLabel.transform.parent as RectTransform; } private void EnsurePrizeStandSettingsInitialized() { _prizeStandSettings ??= CreateDefaultPrizeStandSettings(); _prizeStandSettings.boxes ??= new List(); if (_prizeStandSettings.boxes.Count == 0) { int initialCount = defaultPrizeStandBoxesCount; PrizeStandConnectionInfo connectionInfo = PrizeSystemManager.Instance != null ? PrizeSystemManager.Instance.GetPrizeStandConnectionInfo() : new PrizeStandConnectionInfo(false, 0); if (connectionInfo.IsConnected && connectionInfo.ConnectedBoxesCount > 0) initialCount = Mathf.Clamp(connectionInfo.ConnectedBoxesCount, 1, maxBoxes); for (int i = 1; i <= initialCount; i++) _prizeStandSettings.boxes.Add(new PrizeStandBoxConfig { boxNumber = i, requiredScore = 0 }); } NormalizePrizeStandBoxes(); } private void NormalizePrizeStandBoxes() { List boxes = _prizeStandSettings.boxes; boxes.Sort((a, b) => a.boxNumber.CompareTo(b.boxNumber)); for (int i = 0; i < boxes.Count; i++) { if (boxes[i] == null) boxes[i] = new PrizeStandBoxConfig(); boxes[i].boxNumber = i + 1; boxes[i].requiredScore = Mathf.Max(0, boxes[i].requiredScore); } } private List GetPrizeStandBoxes() { EnsurePrizeStandSettingsInitialized(); return _prizeStandSettings.boxes; } private PrizeStandBoxConfig GetSelectedPrizeStandBox() { List boxes = GetPrizeStandBoxes(); if (boxes.Count == 0) return null; _selectedPrizeStandBoxIndex = Mathf.Clamp(_selectedPrizeStandBoxIndex, 0, boxes.Count - 1); return boxes[_selectedPrizeStandBoxIndex]; } private static string GetPrizeTypeName(PrizeType type) { return type switch { PrizeType.None => "Нет приза", PrizeType.QRCode => "QR-код", PrizeType.Hopper => "Хоппер", PrizeType.PrizeStand => "Витрина", _ => type.ToString() }; } private static string GetTierName(QRTier tier) { return tier switch { QRTier.Bronze => "Бронзовый", QRTier.Silver => "Серебряный", QRTier.Gold => "Золотой", _ => "Не задан" }; } private static int GetTierIndex(QRTier tier) { int index = Array.IndexOf(QRTiers, tier); return index >= 0 ? index : 0; } private static LevelPrizeConfig CopyConfig(LevelPrizeConfig src) { return new LevelPrizeConfig { type = src.type, qrTier = src.qrTier, hopperCapsuleCount = src.hopperCapsuleCount, hopperDelayBetweenCapsules = src.hopperDelayBetweenCapsules }; } private static PrizeStandSettings CopyPrizeStandSettings(PrizeStandSettings src) { PrizeStandSettings copy = new PrizeStandSettings(); if (src?.boxes == null) return copy; for (int i = 0; i < src.boxes.Count; i++) { PrizeStandBoxConfig box = src.boxes[i]; if (box == null) continue; copy.boxes.Add(new PrizeStandBoxConfig { boxNumber = box.boxNumber, requiredScore = box.requiredScore }); } return copy; } private PrizeStandSettings CreateDefaultPrizeStandSettings() { PrizeStandSettings settings = new PrizeStandSettings(); for (int i = 1; i <= defaultPrizeStandBoxesCount; i++) { settings.boxes.Add(new PrizeStandBoxConfig { boxNumber = i, requiredScore = 0 }); } return settings; } } }