Overwatch Tournaments нужны функциональные cookie, чтобы сохранять вход и язык интерфейса. Аналитические cookie необязательны и только показывают, как используются страницы, — подробнее в политике конфиденциальности.

Перейти к содержимому

Регистрация и составы

Заявка на турнир — это форма, окно, статус и допуск, а состав команды — отдельная сущность, которая живёт до балансировщика и драфта. Здесь описаны правила, по которым сервер принимает, отклоняет и достраивает эти данные; полный список маршрутов — в справочнике эндпоинтов.

Окно регистрации

Открытость самостоятельной регистрации определяет ровно одна строка расписания турнира — строка фазы REGISTRATION — плюс один флаг турнира. Текущая фаза турнира в решении не участвует: окно, дотянутое до начала LIVE, само по себе держит регистрацию открытой.

  • Строки нет — закрыто. Это инверсия обычного правила фазовых окон (где отсутствие строки означает «всю фазу»), и она действует только для регистрации: свежесозданный турнир не должен принимать заявки до того, как организатор настроил форму.
  • COMPLETED и ARCHIVED — закрыто всегда, независимо от дат. Ошибка в ends_at не может переоткрыть завершённый турнир.
  • allow_late_registration поднимает только ends_at. Он не открывает турнир без строки REGISTRATION и не открывает окно, которое ещё не началось: «поздняя» регистрация — это всегда после конца, никогда до старта. Исходная дата закрытия при этом остаётся на странице турнира.

Чек-ин — другое окно и другое правило: статус турнира должен быть CHECK_IN и текущий момент должен попадать в строку CHECK_IN. Здесь действует обычный контракт фазовых окон — нет строки или ends_at равен null, значит окно занимает всю фазу.

Форма и заявка

Форма одна на турнир, у неё есть версии, и заявка всегда подаётся против текущей.

МаршрутНазначение
GET /api/v1/tournaments/{tournament_id}/registration/formПубличная форма и каталог саб-ролей; null, если формы нет
POST /api/v1/tournaments/{tournament_id}/registrationПодать заявку за себя (201)
GET /api/v1/tournaments/{tournament_id}/registration/meСвоя заявка или null
PATCH /api/v1/tournaments/{tournament_id}/registration/meПравка собственной заявки, пока она на рассмотрении
DELETE /api/v1/tournaments/{tournament_id}/registration/meСнять заявку
POST /api/v1/tournaments/{tournament_id}/registration/me/check-inПройти чек-ин
GET /api/v1/tournaments/{tournament_id}/registration/listПубличный список участников

Тело заявки — плоская карта ответов плюс версия формы, против которой они собраны. Устаревшую версию сервер отклоняет кодом form_version_stale (409): валидация всегда идёт против текущей схемы.

POST /api/v1/tournaments/42/registration HTTP/1.1
Host: owt.craazzzyyfoxx.me
Authorization: Bearer <токен сессии или API-ключ>
Content-Type: application/json

{
  "form_version_id": 7,
  "answers": {
    "battle_tag": "Player#2100",
    "roles": [
      { "role": "damage", "is_primary": true, "subrole": "hitscan" },
      { "role": "support", "is_primary": false }
    ],
    "identity_discord": "player",
    "stream_pov": true
  }
}

Ключи встроенных вопросов фиксированы: battle_tag, smurf_tags, roles, stream_pov, reserve, public_notes, organizer_notes и identity_<provider> для discord, twitch, boosty, vk, youtube. BattleTag — отдельный встроенный вопрос со своей грамматикой, среди identity_* его нет. Порядок элементов в roles задаёт приоритет ролей; неизвестные и повторяющиеся коды отбрасываются.

Режим флекса у вопроса roles — optional (игрок сам называет роли), all_roles (играбельны все роли, приоритет игрок всё равно указывает) или forced (все роли играбельны и все основные). Форма, у которой не читается схема, ведёт себя как optional.

Остальное, что настраивает форма и что видно в ответе: auto_approve, show_ranks, hide_registrations (публичный список схлопывается в агрегат на сервере), max_participants (справочная вместимость, не лимит — заявка сверх неё проходит), max_substitutes (размер скамейки для командной регистрации), требование открытого профиля с областью main или all, подписочный переключатель со стадией и областью, и правила для команд капитанов: team_rank_min, team_rank_max, team_max_rank_spread, team_unique_identity, team_require_discord_guild.

Статусы

Статусов две независимые шкалы, и каждая — каталог воркспейса: встроенные значения можно переименовать и перекрасить, к ним можно добавить свои.

ШкалаВстроенные значения
registrationpending, approved, rejected, withdrawn, banned, insufficient_data
balancernot_in_balancer, excluded, incomplete, ready

У пользовательских статусов шкалы balancer есть два флага: excludes_from_balancer (заявка не попадает в пул) и excludes_from_ready (заявка не считается готовой). У встроенных первый жёстко задан — истина только для not_in_balancer и excluded, — а второй всегда ложь.

ready и incomplete вычисляются из ролей и рангов и не записываются руками: попытка выставить их напрямую отвечает 400. Единственный путь — добавление одобренной заявки в пул, которое само ставит ready, если у каждой заявленной роли есть ранг, и incomplete иначе.

На одного игрока приходится одна заявка на турнир — это частичный уникальный индекс, а не проверка в коде. Снятие заявки после чек-ина запрещено самому игроку (409); с этого момента снять участника может только организатор. Снятая или отклонённая заявка терминальна: приглашение в команду её не воскрешает.

Допуск

Допуск — это два требования в фиксированном порядке, и порядок виден снаружи, потому что по нему рисуются шаги прогресса.

  1. Открытый профиль. Стадия жёстко зашита как check_in: отдельной колонки под неё нет, пока организаторы её не попросят. Область — main (основной BattleTag) или all (включая смурфов).
  2. Подписка. Стадия берётся из формы (registration или check_in); мусор и null дают check_in, потому что опечатка в настройке не должна начать отказывать всем подряд.

Стадии упорядочены: registration подразумевает и check_in. Само правило подписки живёт на воркспейсе, а не на турнире, — переключатель без правила ничего не требует, а не отказывает всем.

Состояния требования — satisfied, blocked, undetermined, not_applicable. Блокирует только blocked, то есть подтверждённый отказ; всё остальное, включая неизвестный провайдеру ответ, отваливается в undetermined и пропускает. Поэтому:

  • отсутствие учётных данных Discord или Twitch даёт unknown и пропускает — сбой провайдера не должен читаться как отменённая подписка;
  • при mode=any причина сохраняется по каждому провайдеру отдельно;
  • на этапе подачи отказ смягчается, если у игрока ещё остаётся способ подтвердить подписку кодом;
  • при области team действующая отметка покрытия на команде закрывает требование за весь ростер и живой опрос провайдеров не выполняется.

Единственное правило, которое закрывается наглухо, — требование членства в Discord-гильдии для команд: проверка, которую невозможно выполнить, и воркспейс без привязанной гильдии дают блок, а не пропуск.

После чек-ина — пройденного самостоятельно или проставленного организатором — все требования считаются потраченными: заблокированные переезжают из blockers в overridden, а решение становится admitted или not_admitted только по признаку готовности заявки. Пересчитывать требования после чек-ина нельзя: именно так значок «не допущен» когда-то навсегда застревал на игроке, которого организатор впустил вручную.

Решение приходит одним из трёх значений — admitted, pending_check_in, not_admitted. Отказ на шлюзе — 400, по одной ошибке на каждый блокер (не только на первый) с машинным кодом вида open_profile_blocked или subscription_blocked.

Роли и ранги

Роль играбельна, когда она активна и у неё нашёлся ранг больше нуля. Ноль здесь — не «нет ранга»: загрузчик пула отбрасывает ранги <= 0, так что сохранённый ноль дал бы игрока, которого драфт может выбрать, а расчёт баланса молча теряет.

Ранг ищется по слоям, побеждает первый непустой:

  • турнир: заявка → воркспейс → Overwatch;
  • микс: автор → воркспейс → Overwatch.

Отсутствие строки — это и есть наследование, поэтому записи удаляют строку, а не обнуляют её: сохранённый 0 сломал бы проваливание на следующий слой.

Форма ростера

Коды слотов — tank, damage, support, flex. Встроенное значение — {"tank": 1, "damage": 2, "support": 2}.

  • Размер команды — от 1 до 12 слотов. Ростер из одного слота разрешён.
  • draft_rounds равно team_size - 1: капитан уже занимает слот. Для ростера из одного слота это ноль, и живой драфт для такого турнира просто не создаётся.
  • Разрешение: переопределение турнира → значение по умолчанию воркспейса → встроенные 5 на 5. Клиент получает уже разрешённую форму и сам её не пересчитывает — кроме предпросмотра неподтверждённой суммы в редакторе.
  • Блокировки: нельзя менять умолчание воркспейса, пока его наследует турнир с идущим драфтом или формированием, и нельзя менять team_formation или форму ростера, пока активна сессия драфта.

Слот flex — это слот, а не роль: у него нет ранга, и роль в него не записывается. Если каждый слот формы — flex, ролевые счётчики, фильтры и ролевая валидация выключаются целиком.

Команды капитанов

Команда, собранная капитаном на регистрации, — отдельная сущность, не тот ростер, который создают балансировщик и драфт. Капитан основывает команду и регистрируется в ней одним вызовом: та же форма и тот же допуск, что у одиночного участника, а разделение на «создать команду» и «зарегистрировать капитана» оставило бы окно, в котором команда существует без капитана.

POST /api/v1/registration-teams/invites/accept HTTP/1.1
Host: owt.craazzzyyfoxx.me
Authorization: Bearer <токен сессии>
Content-Type: application/json

{ "token": "<сырой токен из ссылки>" }

Приглашения. Два адреса на одной сущности. С target_registration_id это внутреннее предложение конкретному свободному агенту — токена у него нет вовсе, получатель видит его в собственных приглашениях. Без него выпускается ссылка: сырой токен возвращается ровно один раз в ответе на создание, хранится только его sha256, и сравнение идёт в постоянном времени. Срок жизни задаётся ttl_days (по умолчанию 7, допустимо от 1 до 90). Накопительный потолок — 60 приглашений на команду с учётом отозванных и отклонённых; сброс потолка делает организатор, и он сдвигает отсечку по времени, а не удаляет историю.

Приглашение — не заготовка заявки: оно не должно раздувать публичный счётчик участников. Ожидающее приглашение резервирует слот, который ещё можно предложить, но приёму не мешает: приём смотрит на открытые слоты, а не на непредложенные. Само погашение — один сторожевой UPDATE с возвратом строки, поэтому две одновременные попытки открыть одну ссылку не могут выиграть обе; проигравший получает 409. Ограничитель частоты на приёме отваливается в разрешение — настоящая защита здесь энтропия токена.

Игрок, у которого уже есть одиночная заявка, присоединяется к команде существующей строкой: присланное тело заявки при этом игнорируется, его собственные ответы остаются. Предпросмотр приглашения — единственная анонимная поверхность в этом наборе: он разворачивает токен ссылки в турнир, команду, слот, признак запасного, срок и признак «ещё можно принять», не погашая его и не раскрывая ростер. Токен, не совпавший ни с чем, отвечает 404, а протухшее или отозванное приглашение — своим состоянием, а не ошибкой.

Занятость ростера. is_complete считается только по стартовым слотам; запасные живут в отдельном бюджете max_substitutes и слота в форме ростера не занимают. Статус команды — forming, complete, rejected или disbanded; поверх него есть ортогональная ось допуска организатора — pending, accepted, waitlisted. Капитан может заморозить укомплектованный ростер, после чего правки отвечают roster_locked до разблокировки организатором.

Проверки состава считаются только по стартовым: минимальный и максимальный ранг и максимальный разброс (без ранга заявка не проходит минимум и максимум, но в разбросе не участвует), уникальность идентичностей относительно остальных команд турнира и членство в Discord-гильдии.

Экспорт в турнирные команды пропускает команды из списка ожидания, неукомплектованные, пустые и непокрытые подпиской, и каждая пропущенная возвращается с кодом — team_waitlisted, team_incomplete, team_empty, team_subscription_uncovered. Молчаливой потери команды нет. Экспорт здесь разрушающий, а не диффовый, поэтому он отказывается выполняться, если таблица уже посчитана по командам, которые он заменять не будет.

См. также