Реплей Dota 2 — это как “черный ящик” матча: в нем записаны события игры по времени. В этом посте разберем, как скачать реплей, прогнать его парсером Clarity, найти урон между героями, визуализировать таймлайн, а затем автоматически собрать хайлайты и подготовить запись видео.


Что такое реплей Dota 2 и какие данные в нем лежат

Файл записи матча в Dota 2 обычно имеет вид:

  • <match_id>.dem

Внутри лежит набор событий, которые относятся к разным действиям в игре: урон, лечение, покупки, сообщения в чат, переходы состояний и так далее. Именно поэтому задача “проанализировать матч” превращается в задачу “прочитать список событий и сгруппировать их по смыслу”.

Типы событий в реплеях

После парсинга реплей превращается в последовательность JSON-событий. У каждого события есть как минимум:

  • type — тип события
  • time — время в секундах с начала матча

Важно: time может быть отрицательным — так игра может отмечать события, которые происходят “до” основной игровой части (например, вокруг появления крипов).

Ниже типичные типы, которые встречаются при анализе:

Тип события Зачем смотреть
DOTA_COMBATLOG_DAMAGE урон, самое важное для хайлайтов
DOTA_COMBATLOG_HEAL / хил (встречается как соответствующий тип) восстановление здоровья
DOTA_COMBATLOG_MODIFIER_ADD / ...REMOVE эффекты и баффы
DOTA_COMBATLOG_GOLD получение золота
DOTA_COMBATLOG_DEATH убийства
DOTA_COMBATLOG_ITEM поднятие предметов
DOTA_COMBATLOG_PURCHASE покупки
CHAT_MESSAGE_ITEM_PURCHASE, chatwheel, pings, chat контекст из коммуникации
draft_start, draft_timings драфт
interval, actions технические/служебные интервалы

Как скачать реплей Dota 2

Поисковый запрос “сайт чтобы скачать реплей дота 2” чаще всего ведет к двум практичным вариантам.

1) Через клиент Dota 2

В Steam/Dota 2 можно скачать матч в интерфейсе Watch. Вы выбираете MatchID, после чего игра сохраняет файл *.dem на ваш диск.

Обычно путь похож на:

  • ...\Steam\steamapps\common\dota 2 beta\game\dota\replays\

2) Через внешний источник с матчами

Вариант “со стороны сайта” обычно работает так: сервер отдает архив/сжатие .dem, а вам нужно получить обычный *.dem.

На практике это выглядит как:

  1. Скачивание файла формата *.dem.bz2
  2. Распаковка в .dem

Например логика распаковки:

  • bzcat <file>.dem.bz2 > <file>.dem

Как использовать парсер Clarity для обработки реплеев

Чтобы работать с *.dem, нужен разбор. Для этого используют парсер Clarity: он читает события и выдает удобный поток строковых JSON-объектов.

Один из рабочих подходов — поднять парсер в Docker (чтобы не мучиться с зависимостями).

Схема пайплайна

flowchart TD
  A[Скачать replay .dem] --> B[Парсер Clarity]
  B --> C[Получить .jsonlines с событиями]
  C --> D[Python: фильтры и группировки]
  D --> E[Урон между героями]
  E --> F[Визуализация таймлайна]
  F --> G[Кластеризация DBSCAN]
  G --> H[Определение start/end хайлайта]
  H --> I[Подготовка записи видео]

Типичный сценарий в Docker

Идея такая:

  1. Скачиваете/собираете Clarity-парсер
  2. Запускаете локальный веб-сервер парсера
  3. Отправляете .dem внутрь и получаете .jsonlines

Результат удобно хранить рядом с проектом, например как:

  • 6227492909.jsonlines

Python-обработка событий: от потока строк к таблицам

После парсинга матч превращается в примерно сотни тысяч событий. Это нормально.

Пример загрузки .jsonlines

Логика простая: читаем файл построчно и делаем json.loads.

import os, json

REPLAYS_DIR = "../replays/"
dem_path = os.path.join(REPLAYS_DIR, "6227492909.jsonlines")

with open(dem_path, "r") as fin:
    jsonlines = [json.loads(event) for event in fin.readlines()]

print(len(jsonlines))

Быстрая проверка структуры

Первые элементы подсказывают, какие поля есть:

  • time
  • type
  • value (иногда)
  • дополнительные поля для конкретных типов событий (например, имена атакующего и цели)

Как выделить события урона между героями

Главная цель — найти DOTA_COMBATLOG_DAMAGE и отфильтровать только случаи, где:

  • атакующий — герой
  • цель — герой

То есть условия примерно такие:

  • e['type'] == 'DOTA_COMBATLOG_DAMAGE'
  • e['attackerhero'] и e['targethero'] равны True

Формирование таблицы урона

Дальше удобно собрать в pandas DataFrame:

import pandas as pd

df_damage = pd.DataFrame([
    e for e in jsonlines
    if e.get('type') == 'DOTA_COMBATLOG_DAMAGE'
    and e.get('attackerhero')
    and e.get('targethero')
])

Важная тонкость имен героев

В реплеях герой часто называется не “Magnus”, а тех.идентификатором вроде:

  • npc_dota_hero_magnataur

То есть вам придется работать с этими строками или сделать маппинг (таблицу соответствий) отдельно.


Визуализация таймлайна урона

Теперь мы строим простую картинку: “когда” герой наносит урон и “сколько раз/сколько урона”.

Таймлайн урона в точках

Например, берем только урон конкретного героя и строим точки по времени.

import matplotlib.pyplot as plt

mask = df_damage["attackername"] == "npc_dota_hero_magnataur"
df_player_damage = df_damage[mask].copy()
df_player_damage["ones"] = 1

fig, ax = plt.subplots(figsize=(19, 5))
plt.scatter(
    x=df_player_damage["time"] / 60,
    y=df_player_damage["ones"]
)
plt.plot()
plt.show()
  • по оси X: минуты матча
  • по оси Y: факт события (одна точка = одно событие урона)

Деление на “раннюю” и “основную” стадии

Чтобы не смешивать “лайнинг” и массовые драки, можно отрезать начало. Практическая логика такая:

  • примерно до ~10 минут часто меньше ярких массовых моментов
  • основная часть матча дает более плотные кластеры урона

Тогда:

df_player_late_damage = df_player_damage[df_player_damage["time"] > 10 * 60].copy()

И можно сделать “важнее те события, где урон больше”:

fig, ax = plt.subplots(figsize=(19, 5))
plt.scatter(
    x=df_player_late_damage["time"] / 60,
    y=df_player_late_damage["ones"],
    s=df_player_late_damage["value"]
)
plt.plot()
plt.show()

Как применить DBSCAN для кластеризации урона

Идея хайлайтов простая и очень “человеческая”:

хайлайт — это не одно событие, а пачка событий рядом по времени

Если урон идет “сериями” (например, команда навалилась в файте), то события будут собираться в группы.

DBSCAN — алгоритм без учителя для кластеризации: он объединяет точки, которые лежат рядом.

Суть кластеризации для таймлайна

Мы можем кластеризовать не “урон”, а координату по времени:

  • eps задает “максимальную дистанцию” между соседями по времени
  • min_samples задает “сколько соседей нужно”, чтобы точка считалась частью кластера

Пример подхода: считать, что “рядом по времени” — это около 30 секунд.

from sklearn.cluster import DBSCAN

dbscan = DBSCAN(eps=30, min_samples=2)
cluster = dbscan.fit_predict(df_player_late_damage[["time", "ones"]])
df_player_late_damage["cluster"] = cluster

Что мы получим

  • Каждому событию присвоится номер кластера
  • Метка -1 означает “выброс” (обычно отдельные одиночные события)

В терминах таймлайна:
- кластер = потенциальный интервал хайлайта
- выброс = мелочь или нерелевантные события


Как определить начало и конец временных промежутков для хайлайтов

Теперь осталось “склеить” события одного кластера в интервал.

Логика:

  1. Группируем по cluster
  2. берем первое time как start
  3. берем последнее time как end

Пример сборки интервалов

df_player_late_damage["stime"] = df_player_late_damage["time"].apply(
    lambda t: f'{t // 60}:{str(t % 60).zfill(2)}'
)

df_action = df_player_late_damage.groupby("cluster").agg({
    "stime": ["first", "last"]
})

df_action.sort_values(("stime", "first"), inplace=True)
print(df_action)

И получаете список “клипов по времени”, которые затем можно смотреть в игре или превращать в видео.


Идеи для усовершенствования поиска хайлайтов

Алгоритм уже работает, но его можно сделать “умнее”. Вот идеи, которые дают реальный прирост качества:

1) Упорядочить хайлайты по эпичности

Сейчас кластер определяет интервал, но не “насколько он лучший”.

Эпичность можно оценивать суммой урона в кластере:

  • больше value суммарно — вероятнее, что это настоящий файт

Можно сделать дополнительную метрику:

Метрика Что дает
сумма урона в кластере выделяет драки, а не случайные тычки
количество событий урона помогает, когда урон поровну, но событий больше
число убийств в интервале делает хайлайт “жирнее”
наличие модификаторов (бафф/стан) подчеркивает контроль и комбинации

2) Использовать не только урон

Хайлайты — это не только DOTA_COMBATLOG_DAMAGE. Для богатого сюжета полезно добавить:

  • DOTA_COMBATLOG_DEATH
  • события модификаторов (стан, ультимейты и т.п.)
  • возможно, события про покупки или резкие всплески золота

Тогда DBSCAN будет кластеризовать “интересные события”, а не только урон.

3) Улучшить параметры eps

Обычно “eps=30” работает как старт. Но разные стадии матча могут вести себя по-разному:

  • в лейнинге драки редкие
  • в мид-гейме плотность выше

Значит eps можно адаптировать:
- например, делать разные eps до и после определенной минуты


Как реализовать автоматическую запись видео моментов

Когда интервалы хайлайтов найдены (start/end), остается задача превратить их в видео.

Вместо ручных действий в клиенте можно использовать консольные команды перемещения по демке, например:

  • demo_goto
  • demo_gototick

Логика процесса такая:

  1. Запускаете матч-демо
  2. Делаете переход на start
  3. Включаете запись на время end - start
  4. Повторяете для каждого кластера

Мини-схема для автоматизации

flowchart LR
  A[Интервалы start/end из DBSCAN] --> B[demo_goto на start]
  B --> C[Запись от start]
  C --> D[demo_gototick на end]
  D --> E[Останов записи]
  E --> F[Файл видео для хайлайта]

Короткое резюме процесса

Шаг Что делаем Результат
1 скачиваем match_id.dem исходный файл реплея
2 прогоняем через Clarity поток JSON событий
3 фильтруем DOTA_COMBATLOG_DAMAGE таблица урона между героями
4 строим таймлайн график “когда били”
5 DBSCAN по времени кластеры “пачек” урона
6 start/end по кластерам список интервалов хайлайтов
7 запись демо командой готовые видео фрагменты

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