Недавно в Dota 2 появилась возможность нарезать видео-ролики в формате .mp4 при просмотре записей матчей. Я не удержался и решил сделать простой алгоритм поиска интересных моментов aka хайлайтов. Вот что из этого получилось на примере последней карты гранд-финала The International 2021, где Collapse из Team Spirit катал PSG.LGD на своем Magnus'е. Видео ускорено в 1.5 раза. Формат записей матчей в Dota 2 Парсинг реплеев Анализ событий матча Кластеризация методом DBSCAN Идеи по усовершенствованию подхода Ссылки Запись матча в Dota 2 называется Replay и представляет из себя файл <match_id>.dem с набором protobuf-событий: клики, урон, хил, сообщения в чат и так далее. Если подсунуть файл клиенту игры, то он воспроизведет все события соответствующего матча. Это замечательная фича, которая позволяет вам открыть пивко и залипнуть в годную каточку после тяжелого дня расковырять структуру реплеев. Но мы сегодня протобафы ковырять не будем. Вместо этого воспользуемся результатами умельцев из комьюнити (ссылка на репозиторий в конце статьи). Естественно умельцы не остановились на достигнутом и реализовали парсер с говорящим названием Clarity. Он написан на Java и представляет из себя набор processor'ов, обрабатывающий события разных типов. Чтобы не возиться с настройками, поднимем контейнер в Docker. Скачаем репозиторий. Запустим Docker. И выполним build-скрипт.  У нас появился парсер.Теперь нужен реплей матча. Есть несколько способов его получить. I. Скачать через клиент игры Вы можете скачивать реплеи через вкладку Watch в клиенте игры. Я использовалmatch_id = 6227492909. Результат сохраняется в корневую папку игры. Пример пути на машинах под Windows. II. Скачать с OpenDota Есть возможность скачать реплей с сайта OpenDota (ссылка в коцне статьи). Ребята эмулируют поведение игрового клиента и вытаскивают ссылки на CDN Valve. Последний в свою очередь отдает файлы в сжатом формате, поэтому их нужно предварительно распаковывать. Допустим мы справились с поиском .dem файла. Прогоним его через парсер. На выходе получим JSON'ы, разделенные символами переноса строки и сохраненные в отдельный файл .jsonlines. Для удобства дальнейшего анализа расчехлим Python и Jupyter. Считаем файл с предыдущего шага и посчитаем количество событий. 40-минутный матч превратился в ~200k событий. Посмотрим на их структуру. Видим, что разные события имеют разные поля. Но все события имеют поля type — тип события и time — время в секундах с начала матча. Стоит отметить, что время может принимать отрицательные значения. Это позволяет отделять события до и после выхода крипов (00:00 по часам матча). Посчитаем количество событий разных типов. Я предположил, что интересными могут оказаться те моменты, когда герои наносят друг другу урон. Рассмотрим подробнее события DOTA_COMBATLOG_DAMAGE. Для начала посчитаем, сколько урона игроки нанесли друг другу. Полученные значения я сравнил с выводами на все том же сайте OpenDota и остался доволен, потому что они совпали. Обратим внимание, что в игре используются текстовые идентификаторы персонажей, которые могут не соответствовать привычным именам. Например, герой Magnus в реплеях использует кодовое имя npc_dota_hero_magnataur. Визуализируем таймлайн урона от игрока Collapse на Magnus по персонажам других игроков. Матчи в Dota 2 можно условно разделить на 2 большие стадии: лайнинг и основная. В случае Collapse это разделение проходит по границе ~10 минут с момента выхода крипов. Герой Magnus раскрывается как раз в основной стадии за счет покупки Blink Dagger и обилия массовых драк. Поэтому отсечем события до 10 минуты, а также увеличим размер точек в зависимости от нанесенного урона. Заметим, что ощутимую часть времени персонаж не наносит урон. А значит и шансы обнаружить хайлайты в эти моменты крайне невелики. С другой стороны, события нанесения урона образуют достаточно плотные группы. Интуиция подсказывает, что эти группы и есть потенциальные клипы с хайлайтами. Все что требуется — найти начало и конец каждого клипа. Задачу можно свести к кластеризации — методу машинного обучения без учителя, где общая идея заключается в том, чтобы разделить исходную выборку на подмножества похожих объектов — кластеры. Алгоритмов кластеризации достаточно много, ниже иллюстрация результатов работы некоторых из них на синтетических данных. Мы воспользуемся алгоритмом DBSCAN. Суть проста: объединить объекты, которые находятся - окрестности друг друга. Расстояние между объектами можно считать с помощью разных метрик, но в нашем примере хватит самой привычной — Евклидовой. Причем мы не будем использовать абсолютное значение урона, а только время события. Т.е. для мы просто группируем события с интервалом не более ~30 секунд. Вторым важным параметром алгоритма является min_samples. Он определяет минимальное число объектов в кластерах. Если вокруг точки мало соседей, то ей присваивается метка -1 — выброс. В данном примере можно взять min_samples = 1 и ничего не сломается, но на практике это может привести к клипам с хайлайтами, у которых начало будет совпадать с концом. Осталось только вспомнить, что кластеры в данном случае — временные промежутки матча. Для нарезки клипов с хайлайтами выделим начало и конец каждего кластера. Успех! Осталось воспользоваться фичей игры и нарезать клипы. Итоговое видео вы уже видели в начале статьи. Внимательный читатель заметит, что кластеризацию мы использовали скорее для общего развития. Можно потюнить параметры алгоритма или вовсе изменить подход для поиска временных промежутков с потенциальными хайлайтами. Некоторые моменты получились не очень насыщенными. Как отсортировать временные промежутки по эпичности?Подсказка: можно использовать не только события урона. Как реализовать автоматическую запись видео, чтобы не приходилось запускать клиент игры и накликивать руками?Подсказка: существуют консольные команды demo_goto, demo_gototick. В следующей части мы увеличим масштаб и напишем сервис для параллельного парсинга реплеев на Celery и Flask. Мой канал в Telegram Структура protobuf Clarity Fork Clarity от OpenDota Матч Team Spirit vs PSG.LGD на OpenDota Ссылка на реплей Описание алгоритма DBSCAN Документация по DBSCAN в scikit-learn Примеры метрик  Про AI для игр: https://t.me/savostyanov_dmitry