This commit introduces a new Discord event announcer to the backend, allowing for event announcements via Discord. It includes the addition of new environment variables for Discord configuration in `.env.example` and `compose.yaml`. The `main.go` file has been updated to initialize the announcer, and a new `discord` package has been created, containing the announcer logic and tests. Additionally, the service layer has been modified to support bracket draft management, enhancing the overall event workflow. Integration tests have been updated to ensure proper functionality of the new features.
13 KiB
Предметная модель
Основные сущности
Player
Игрок связан с Discord-аккаунтом и имеет отображаемое имя и собственный Overwatch-ранг для каждой роли: Tank, Damage, Support. Лестница содержит 40 значений от Bronze 5 до Champion 1: Bronze, Silver, Gold, Platinum, Diamond, Master, Grandmaster, Champion, каждый с дивизионами 5 → 1. Для вычислений значения преобразуются в ordinal 1–40.
Игрок редактирует ранги самостоятельно; Admin может видеть время последнего изменения. Игрок выбирает любое число предпочитаемых ролей, до трёх желаемых союзников и до трёх игроков для avoid. Предпочтения и avoid являются мягкими пожеланиями, а не гарантией состава; один игрок не может одновременно находиться в обоих списках.
Account
Учётная запись Discord с глобальной ролью Player, Moderator или Admin. Moderator имеет те же операционные права, что Admin, но не может назначать и снимать модераторов. Moderator назначается только Admin; начальные Admin задаются списком Discord ID в конфигурации развёртывания. Изменение роли записывается в аудит.
Event
Запланированный игровой вечер: название, дата и время, часовой пояс, описание, дедлайн регистрации, формат команд, регламент, турнир и текущее состояние.
EventRegistration
Отметка игрока в календаре со статусом Going, Maybe или NotGoing. Игрок меняет собственный статус, а Admin может изменить его за игрока. Регистрация хранит автора и время последнего изменения, чтобы интерфейс явно показывал административное действие. В момент закрытия регистрации Admin подтверждает список участников; только Going-игроки по умолчанию попадают в балансировку.
Discord-анонс события
Созданный микс проецируется во внешний Discord-канал как информационный анонс со ссылкой на страницу Event. Анонс не является источником состояния регистрации: RSVP, доступность события и все переходы workflow по-прежнему определяет backend Mixmaker. Недоступность Discord не отменяет валидное создание Event.
Жизненный цикл события
Событие проходит серверно контролируемые состояния:
RegistrationOpen— игроки меняют RSVP, Admin добавляет гостей;RegistrationClosed— список участников зафиксирован, обычные RSVP заблокированы;Balancing— создаются варианты команд и резерв;RostersDraft— Admin выбирает вариант, меняет игроков одной роли местами и заменяет их резервными;RostersConfirmed— составы 1/2/2 и капитаны проверены и заблокированы;BracketDraft— staff задаёт порядок Bo3 и источники Team / Winner / Loser для слотов;Live— созданы готовые серии, разрешены только действия текущего шага жеребьёвки, банов или результата;Completed— выполнены все запланированные матчи;Cancelled— Admin отменил событие; оно остаётся в календаре, но игровые действия заблокированы.
Переходы выполняются отдельными командами API с проверкой ожидаемой версии. До запуска скрима staff-пользователь может вернуться на один этап назад: черновик удаляется при возврате из RostersDraft, а состав снова разблокируется при возврате из RostersConfirmed. Возврат из Live и Completed запрещён. Нельзя начать скрим без минимум двух полных команд, валидного состава 1/2/2 и капитана в каждой команде. После RostersConfirmed обычная перестановка запрещена. Аварийная замена во время Live доступна staff-пользователю, должна сохранять ролевой состав и записывается в аудит. Полное удаление события является отдельной staff-командой и каскадно удаляет его RSVP, составы, roster draft, серии, турнир и event-scoped аудит.
Team
Состав игроков, назначенный staff-пользователем капитан, цвет/название и рассчитанные показатели силы. Капитан должен входить в текущий состав команды; переназначение фиксируется в аудите. После запуска скрима капитан может задать своей команде отображаемое название; staff может переименовать любую команду, изменение синхронизируется с live- и spectator-экранами.
- общий средний рейтинг;
- средний рейтинг по каждой роли;
- распределение игроков по ролям;
- оценка доверия к балансу.
Ruleset
Версионируемый набор правил серии:
- формат Bo3;
- последовательность режимов;
- пулы карт по режимам;
- порядок банов карт и героев;
- лимиты банов по ролям;
- ограничения повторных банов в серии.
Series
Матч двух команд в рамках события или турнирной сетки. Хранит жеребьёвку, порядок первого бана, карты, счёт, статус и итогового победителя.
MapResult
Исход отдельной карты: победившая команда либо ничья, счёт/заметка при необходимости и автор фиксации. Результат карты обновляет счёт серии; завершённый Bo3 определяет победителя автоматически. Организатор может исправить ошибочный результат, при этом изменение попадает в аудит.
MapDraft
Для первой карты — пошаговое исключение карт из пула до одной оставшейся. Вторую и последующие карты напрямую выбирает команда, проигравшая предыдущую карту; после ничьей право выбора получает команда, проигравшая начальный жребий. Ранее сыгранные карты исключаются.
HeroDraft
Четыре бана перед картой в порядке A → B → A → B. Каждая команда выбирает двух героев разных ролей. Героя, которого команда уже банила в этой серии, она не может забанить снова; героя, ранее забаненного соперником, банить можно.
Tournament
Versioned граф матчей, в котором каждый слот ссылается на конкретную Team либо Winner/Loser матча из предыдущей колонки. Готовые зависимости материализуются в отдельные Bo3-серии автоматически; несколько готовых матчей могут идти параллельно. Последняя колонка содержит один матч, победитель которого становится общим победителем. Для трёх команд стартовый шаблон проводит A–B, затем Loser(M1)–C, затем финал победителей первых двух матчей.
Правила текущего регламента
Карты
Базовая последовательность Bo3:
- Control;
- Hybrid или Escort;
- Control-тайбрейкер, если счёт 1:1.
Альтернативная последовательность: Control → Push → Escort.
Первый бан первой карты определяется жеребьёвкой. Затем команды по очереди исключают карты внутри пула режима, пока не останется одна. После завершения карты FSM переходит в MapPick: проигравшая команда выбирает следующую доступную карту, после чего начинается hero draft.
Герои
- перед каждой картой банятся четыре героя;
- порядок: A → B → A → B;
- каждая команда банит двух героев разных ролей;
- один и тот же герой может быть забанен командой только один раз за серию;
- бан героя соперником не расходует собственную возможность забанить этого героя позже.
Балансировка
Целевая функция должна учитывать не только близость общего среднего рейтинга команд, но и:
- разницу средних рейтингов по ролям;
- корректность состава 1 Tank / 2 Damage / 2 Support;
- назначение игроков на предпочитаемые роли;
- разведение по разным командам игроков из avoid-списков;
- попадание выбранных игроком союзников в одну команду;
- запрет повторения составов при регулярных играх;
- заранее заданные пары или ограничения «вместе/раздельно».
Алгоритм должен возвращать не только составы, но и понятную оценку качества и причины оставшегося дисбаланса.
Жёсткие ограничения 1/2/2 и уникальности игроков всегда важнее предпочтений. Разница рейтингов имеет больший вес, чем пожелания. В MVP используется детерминированная эвристика: несколько начальных назначений, взвешенная целевая функция и локальные обмены игроков одной роли. Архитектура допускает замену на CP-SAT/MILP solver.
Ручная корректировка не изменяет исходные варианты балансировщика: выбранный вариант копируется в редактируемый roster draft. Обмен допускается только между слотами одинаковой роли. Замена из резерва назначает игрока в конкретный ролевой слот и пересчитывает показатели обеих команд и резерва.
Доменные события
- EventCreated;
- PlayerJoinedEvent;
- EventRegistrationChanged;
- TeamsBalanced;
- TeamCaptainAssigned;
- CoinTossResolved;
- MapBanned;
- MapSelected;
- HeroBanned;
- MapResultRecorded;
- MapResultCorrected;
- SeriesCompleted;
- BracketAdvanced.