53 lines
7.7 KiB
Markdown
53 lines
7.7 KiB
Markdown
# Логика работы центральных 6 игровых портов в клиентской части на React
|
||
|
||
Основной компонент портов — `GamePort.tsx` (рендерится 6 раз в Dashboard для портов 1-6). Каждый порт показывает статус: свободен (серый), игрок готов (зелёный), активная игра (синий/оранжевый/голубой в зависимости от типа). Логика построена на Redux (состояние портов в store/gameSlice), WebSocket (отправка команд на сервер), модалах и drag&drop. Права доступа: оператор/админ — полные, техник — только пристрелки (game=1 или 2, без игроков/обычных игр).
|
||
|
||
## 1. Клик по свободному порту (создание новой игры)
|
||
- Пользователь (оператор/админ) кликает по порту → проверка: смена открыта? (systemState.smena=true), пауза выключена? (systemState.pause=false), ESP подключён? (systemState.esp !== false).
|
||
- Если техник: показывается модал подтверждения "Техническая пристрелка?" → если да, отправка WS: `{do: 'start', line: portNumber, game: 2, patr: 0, gamerId: null, isBonus: false}` (запуск пристрелки без игрока).
|
||
- Если оператор/админ: открывается модал `GameSelectionModal.tsx` (onStartGame(portNumber, false)).
|
||
- В модале:
|
||
- **Шаг 1: Выбор типа игры** (категории хардкод в GAME_CATEGORIES: id=10 "По банкам", id=11 "По мишеням", id=12 "Беспроигрышные"; или бонусные, если isBonus=true).
|
||
- Категории берутся из статичного массива (GAME_CATEGORIES).
|
||
- Кнопки категорий: градиентные карточки с иконками (fas fa-bullseye и т.д.), счётчиком игр (фильтр по gameInfo.games).
|
||
- Если смена не открыта — кнопки заблокированы, сообщение "Откройте смену".
|
||
- Выбор категории (handleCategorySelect) → переход к шагу 2, setCurrentStep('game').
|
||
- **Шаг 2: Выбор конкретной игры** (игры из API).
|
||
- При открытии модала: fetch('/api/game-info') → gameInfo = {groups: {10: {title: "По банкам"}, ...}, games: {_id: 99, group: 10, patr: 20, title: "Малая призовая", cost: 400, isGameActive: "вкл", ...}}.
|
||
- Фильтр: game.group === selectedCategory, isGameActive === "вкл", !isTestGame(_id !==1 && !==2).
|
||
- Сортировка по _id, setAvailableGames (requestAnimationFrame для плавности).
|
||
- Кнопки игр: карточки с названием, патронами (patr), сложностью (по patr: 1=лёгкая, 2=средняя, 3=сложная), ценой (cost ₽, бесплатно для бонуса).
|
||
- Выбор игры (handleGameSelect) → setSelectedGame(game), переход к шагу 3, setCurrentStep('payment').
|
||
- **Шаг 3: Выбор типа оплаты** (если !isBonus).
|
||
- Кнопки: "Наличные" (cash, зелёная) или "Безнал" (card, синяя), с иконками (fa-money-bill-wave, fa-credit-card).
|
||
- Для бонуса: оплата не нужна, но шаг показывается с "БОНУС".
|
||
- Выбор (handlePaymentSelect) → формирование команды.
|
||
- **Запуск игры**: отправка WS (sendMessage): `{do: 'start', line: portNumber, game: selectedGame._id, gamerId: port.gamer.gamerId (или null), isBonus: isBonus, tel: user.phone, patrons: selectedGame.patr, paymentType: 'cash'|'card'}`.
|
||
- Модал закрывается (onClose), порт обновляется из WS-ответа (статус "активная игра", таймер start).
|
||
|
||
## 2. Работа с активным портом (во время игры)
|
||
- Показ: номер порта, название игры (getGameName: e.g. "Малая призовая" для id=99), тип (бонус/обычная), игрок #gamerId, таймер (useEffect interval 1000ms от startTime), патроны (patr), статистика (patrOk хитов, patrAdd добавленных).
|
||
- **Изменение патронов**: кнопки +/- (если canManageGame, !paused) → WS `{do: 'addp', line: portNumber, patr: +1|-1}`. Уведомление "добавлено/убрано 1 патрон".
|
||
- **Кнопки управления** (верхний правый угол, если canCloseGame):
|
||
- "Перенести игру" (IoSwapHorizontalOutline, если canDragGame): dispatch(gameMovingStarted), вход в режим drag (или кнопочный movingGame=portNumber).
|
||
- "Отменить игру" (IoClose): модал ConfirmationModal(type='cancelGame') → WS `{do: 'cancelGame', line: portNumber}`.
|
||
- "Удалить" (IoTrashOutline, для порта с игроком): модал PlayerDeleteModal → WS `{do: 'deleteGamer', gamerId, line: portNumber}`.
|
||
- **Завершение игры**: модал ConfirmationModal(type='endGame' или 'forceEndGame') → WS `{do: 'endGame'|'forceEndGame', line: portNumber, priz: true/false}` (с призом/без).
|
||
- **Подсветка**: ring-4 по типу (зелёный hit, жёлтый shot, красный miss) из port.highlight (из WS).
|
||
|
||
## 3. Перенос игры/игрока (drag&drop или кнопка)
|
||
- Если movingGame/movingPlayer !== null (из Redux): клик по свободному порту → анимация (createFlyingPortAnimation: полёт от source к target, частицы takeoff), задержка 400ms → WS `{do: 'move', stopLine: from, startLine: to}` (для игры) или `{do: 'moveGamer', oldLine: from, newLine: to}` (для игрока).
|
||
- Drag: draggable=true (если canDragGame/Player), handleDragStart (set dataTransfer JSON {type, fromPort}), handleDrop (парсинг, анимация, WS).
|
||
- Визуал: source — opacity-50 scale-98, target — ring-green bg-green/20, overlay "Переместить сюда".
|
||
- Права: оператор — игры (кроме tech=2), игроки; техник — только пристрелки (1/2).
|
||
- После: dispatch(movingCancelled), уведомление "Перемещено".
|
||
|
||
## 4. Общие проверки и обновления
|
||
- WS-ответы обновляют Redux (ports[portNumber]: game, gamer, patr, startTime, etc.).
|
||
- Пауза (systemState.pause): все действия заблокированы, grayscale opacity-50.
|
||
- Бонус: onStartGame(portNumber, true) → модал с isBonus=true, игры все (без фильтра), бесплатно, paymentType игнорируется.
|
||
- Таймер: только для active (diff от startTime).
|
||
- Модалы: ConfirmationModal/PlayerDeleteModal — для подтверждений, с деталями (игра, время, счётчик игр).
|
||
- Анимации: 3D-эффекты (perspective rotate), glow для игроков, pulse для highlight.
|
||
|
||
Логика фокусируется на UI (Tailwind, responsive mobile/tablet/desktop), правах ролей и WS-синхронизации с сервером. Нет прямого доступа к серверу в клиенте — только fetch для gameInfo и WS для команд. |