Автозапись демок

Kruzya

Участник
Сообщения
12,973
Реакции
10,927
  • Команда форума
  • #1
Предисловие
Примерно чуть более года назад, просматривая в очередной раз тему уже имеющегося плагина автодемок для CS:GO, появилась идея, что надо написать свои автодемки, с блекджеком и шлюзами. Примерно так начался проект, конец которого пока не наступил.
Первая ориентировочная дата завершения работ была "конец 2018 года", но, как видите, уже август 2019-го (на момент написания поста), а моей замены всё ещё нет.

Как так?
Моя концепция автозаписи демок была немного иная, чем у уже существовавшего плагина. Предполагалось, что плагин будет чисто "дёргать" SourceTV/GOTV на запись/завершение записи демки и хранить рядом с файлом - файл с "метаданными", которые как-либо описывают содержимое: имя карты, кол-во записанных тиков, время записи, перечень игроков, все произошедшие события (слишком громко сказано, "все". подробнее ниже).
Т.е., у плагина снята какая-либо зависимость от веб-панели. Он просто записывал, сохранял метаданные, а веб (уже неважно, какой: самописный с полностью иной архитектурой работы, или "от автора плагина") периодически стучался на сервер и забирал все записанные полностью отсутствующие у него демки, удаляя их с сервера (для экономии дискового пространства).
И если основа плагина была завершена приблизительно уже в августе 2018-го, то с веб-панелью примерно тогда же было решено повременить, пока ядро не будет протестировано хотя бы базово.

Начало октября 2018-го. Очередной учебный год (кто не знал - я студент). Плагин так и не был протестирован (попросту некогда), но решено было начать хоть что-то делать у веб-панели. Создана доска на Trello, отметил что надо сделать, примерно накидал структуру базы. Начались кое-какие подвижки по вебу.
Т.к. ранее я фреймворки для веб-разработки не использовал для написания приложения полностью с нуля, то решил научиться на примере демок, чтобы хотя бы не велосипедить ту же работу с запросами. Начал долго и усердно выбирать. Остановился на Nette. Это был довольно интересный фреймворк, с интересным шаблонизатором Latte (интересен он синтаксисом, но сейчас не об этом). Даже начинал что-то делать (был реализован примерно конфиг для обработчиков событий (см. ниже), абстрактный класс для обработчиков событий и что-то ещё. До шаблонов руки так и не дошли.
Почему не Laravel? Ну, он был слишком перегруженный для моей задачи. Я бы и 80% функционала фреймворка не использовал, как мне кажется. Плюс ещё в начале проекта, я обозначил себе мини-требования (минимально поддерживать PHP 5.5, ну или хотя бы 5.6). Тянуть старую версию Лары точно был не вариант.

Декабрь. Тот же 2018-ый. Пора, наверное, поторапливаться, ведь конец обозначенного в первую неделю срока, а что готово? Только плагин, не оттестированный полностью, и кое-какие нужные штуки под капотом у веба, которые в чеклистах на Трелло не были никак обозначены, а потому и не отмечены. Но времени снова нет. Сессия. Подготовка к Всероссийской олимпиаде по IT (в которой я, к слову, занял 7-ое место по всей России среди 58-и участников. Успех!).

Январь 2019-ый. Срок просран, даже не протестировано ничего. И да, снова подготовка к олимпиаде + практика. Время вроде есть (по выходным), но как-то абсолютно не до этого.

Май 2019-ый. Этот месяц был немного богат на события. Почему?
  • Ведётся работа над своим "фреймворком" на базе уже имеющихся компонентов. Почему я заключил "фреймворк" в кавычки? Под капотом использовался Slim (микро-фреймворк). На него просто насаживал приятные (лично для меня) библиотеки по работе с той же базой и ФС. Он кстати уже работоспособен, и на его базе можно построить какое-нибудь небольшое приложение (те же демки).
  • К мёртвому (пока что) проекту присоединяется @Larsalex и предлагает свои варианты фронта. Это как глоток свежего воздуха для меня, ибо я в фронте - ни-ни. Если он не будет против - чуть позже покажу здесь варианты.
И... Всё. Пока что. Дальше работа, работа, и ещё раз работа. Периодически что-то делается на вебе, но не так активно.
Сегодня 3-ое августа 2019 года. В теме с теми самыми старыми кривыми (как я считаю) демками поднимается повторно тема, мол я что-то делал. И сейчас я пишу этот пост.

Архитектура
Немного подробнее про архитектуру.
Как я уже сказал несколько раз выше, плагин чисто записывает демки и ведёт файл с метаданными. Метаданные описывают, что включает в себя демо-файл.
Примерная структура метаданных такая (она ведётся в JSON; прокомментировал для понимания, что есть что):
JSON:
{
    // Текущая карта
    "play_map":    "vsh_kakariko_pb3a",

    // Количество записанных тиков (кадров)
    "recorded_ticks": 43965,

    // Уникальный идентификатор записи.
    // Используется исключительно для обеспечения уникальности записи.
    "unique_id": "0a71b8d7-57f5-4552-8578-9d84b39b9bf9",

    // Время начала записи с эпохи UNIX (01.01.1970 03:00 MSK) в секундах.
    "start_time": 1564825141,

    // Время завершения записи с эпохи UNIX в секундах.
    "end_time": 1564825807,

    // Перечень всех игроков, которые попали на демку.
    "players": [
        {
            // Account ID игрока.
            "account_id": 1111331224,

            // Является ли игрок "ботом".
            // По факту, от этого поля можно избавиться, но пока не помню почему, но не избавился.
            "is_bot": false,

            // Имя игрока.
            "name": "CrazyHackGUT aka Kruzya"
        },

        // ... ещё другие игроки ...
    ],

    // Все записанные события.
    "events": [
        {
            // Имя события.
            "event_name":    "Core:RoundStart",

            // Время.
            "time":            1564825147,

            // Данные события.
            // Просто ассоциативный массив.
            // Может быть пустым, если событие не предполагает какие-либо данные.
            "data": {}

            // Ещё планировалось очень давно записывать тик, но пока не сделано. Поле будет "tick".
        },

        // ... ещё другие события ...
    ]
}
Все записи (включая метаданные) должны храниться в /addons/sourcemod/data/demos/.
Одна демка состоит гарантированно из двух файлов:
  • dem-файл. Сама демка.
  • json-файл. Метаданные.
Ещё может существовать lock-файл. Он сигнализирует о том, что демо-запись записывается прямо сейчас. Возможно, имеет смысл избавиться от него, т.к. если демка записывается, то у неё нет json-файла. Можно по этому признаку ловить.
Все файлы в имени содержат UUID v4.
Список событий ядром не формируется. Для этого используются модули, которые так же управляют состоянием, записывается демка или нет.
Они же (модули) передают имя события и данные, которые должны уйти в JSON-файл.

К ядру приложен базовый модуль (AutoDemo/EventManager.smx, он же AutoDemo_EventManager.sp), который:
  1. Способен (если ядро работоспособно) начинать и завершать запись при смене карты или раунда. Для настройки этого существует консольная переменная sm_autodemo_recordmode.
  2. В метаданные записывает события убийства игрока и сообщения в чат.
Плагин доступен на GitHub.

Что дальше?
Текущий плагин автозаписи демок, которым пользуются уже очень давно, оставляет желать лучшего. Он не подразумевает большой кастомизации, да и его настройка - один большой сплошной геморрой.
Документации нет, всё собиралось и настраивалось методом научного тыка. Плагин прибит гвоздями к CS:GO, которая отнюдь не является единственной игрой, где подобный плагин бы пригодился, и общается с SourceTV (GOTV) посредством выполнения консольных команд, результат исполнения которых никак не обрабатывается (иногда полезно, иногда нет).

Что я хочу предложить... Хоть я и скриптер, но тестового сервера у меня нет (и не будет). А даже если бы и был, мне нужны реальные игроки, чтобы постоянно не прописывать в ядро дикие костыли, которые бы имитировали реальных игроков для записи в мета-данные.
Я хочу попросить у сообщества помощи в завершении работы над данным плагином и вебом. Сейчас я выкладываю лишь плагин, ибо веб в очень глубокой альфе (хотя кое-какие подвижки по нему идут). Пока хочется понять, работает ли вообще ядро, или нет.

И, напоследок. Кому хотел бы сказать "Спасибо".
  • @ThreshHNS. Выложил первоначальную версию плагина и веба, которые вечно заставляли задуматься над реализацией своего.
  • @Rabb1t. Помог немного с составлением README-файла у репозитория и заполнил в файле события для CS:S, CS:GO.
  • @The_C@t. Я бы, наверное, и не решился сегодня написать этот тред, если бы не пост этого парня. И плагин продолжал бы висеть в альфе на "тестах". Так может хоть протестируем наконец его.
  • @Larsalex. Помощь по фронту.
Установка
Этому плагину необходимо два расширения.
  • SourceTV Manager. Позволяет управлять GOTV/SourceTV через движок, без команд.
  • REST in Pawn. По факту, используется только JSON-модуль из RiP. Необходим для формирования метаданных.
Устанавливается легко и просто: загрузить все бинари на сервер, загрузить плагины с расширениями в память. Лучше всего перезагрузить сервер.

Реквизиты
Деньгами время не выиграть. Но его хотя бы можно отбить, и ими же понять, что это действительно кому-то нужно, а потому идею развивать стоит и дальше.
Реквизиты я не публикую здесь в теме. Если кто готов - велкам в ЛС.

Обсудить...
Хотите обсудить плагин/веб или предложить фичу вне этой темы? Милости просим в наш чат Telegram:

И ещё раз: репозиторий на GitHub. Веб будет выложен отдельно от плагина, поскольку явной привязки плагина к вебу делать не планируется.

Вроде всё основное, что хотел написать - написал...
 
Последнее редактирование:

Rabb1t

Амбассадор
Сообщения
2,968
Реакции
1,429
  • Команда форума
  • #2
Ждем в ресурсах.
 

Kruzya

Участник
Сообщения
12,973
Реакции
10,927
  • Команда форума
  • #3
В ресурсах появится только после полного тестирования, исправления всех основных багов и завершения работ над вебом.
Ясное дело, плагин будет выложен отдельно от веба, поскольку первый не прибит гвоздями к последнему.
 
Последнее редактирование:

xados

Участник
Сообщения
316
Реакции
32
так как я понял, веба пока что еще нету? я бы с удовольствитем протестировал плагин, но есть ли возможность прикрутить его к GameCMS? если да, то как такое сделать) а то именно в этом я не шарю.
 

Truyn

King of clowns
Сообщения
1,591
Реакции
749
Это все будет работать с https как понимаю?
 

The_C@t

Слабоумие и отвага
Сообщения
272
Реакции
142
Если кому то из веберов это было бы интересно (то что у Крузи нема времени я представляю) дозапилить вебморду умеренной дырявость - готов существенно проспонсировать разработку вебморды в паблик (понять бы ценник) мож ещё кто-то присоединится. Дело хорошее.
С тестом плагина постараюсь помочь.

P. S.
С донатом вернусь, как буду на работе
 

Kruzya

Участник
Сообщения
12,973
Реакции
10,927
  • Команда форума
  • #8
так как я понял, веба пока что еще нету?
Есть какие-то примерные наброски, но в целом да - веба нет.

я бы с удовольствитем протестировал плагин, но есть ли возможность прикрутить его к GameCMS?
Плагин не прибит гвоздями к какой-либо веб-панели. Если Стас (или кто-то иной) реализует интеграцию с плагином - да, конечно.

Это все будет работать с https как понимаю?
Плагину наплевать, по какому протоколу будет доступен Ваш сайт (хоть голый TCP), и есть ли он у Вас вообще. Так что - да! =)
 

Kruzya

Участник
Сообщения
12,973
Реакции
10,927
  • Команда форума
  • #9
Протестировали поверхностно. Основное, кажется, работает (после фиксов). Но надо посмотреть, как плагин себя вести будет и в других играх.
Тесты проводились в CS:S v90, Steam.

В JSON структуре были произведены некоторые изменения. Убрал запись ботов (непонятно, как их однозначно идентифицировать, если в виде основного ключа у меня выступает Account ID), добавил что-то вроде data структуры из событий, но для игроков. Туда в будущем планируется записывать счёт игрока, киллы и т.д..

Качать необходимо сам репозиторий, и компилировать так же вручную. В релизах пока старые билды с некоторыми багами.
 

Kruzya

Участник
Сообщения
12,973
Реакции
10,927
  • Команда форума
  • #10
Тестировали на Винде, где всё работало нормально. На Линуксе столкнулись с проблемами в виде неработающего расширения.
Гуглёж привел к тому, что не слинковалась одна из библиотек самого движка. Перекомпиляция под GCC не помогла, зато с CLang - завелось.
Прикольно, конечно.

Приложил перекомпилированный SourceTV Manager v1.1.1 (свежее, чем для Винды даже, да) под Linux. Собиралось на Ubuntu 16.04.6.
44291
 

Вложения

  • SourceTV Manager v1.1.1 (linux).7z
    1.6 МБ · Просмотры: 169

The_C@t

Слабоумие и отвага
Сообщения
272
Реакции
142
Последнее редактирование:

Kruzya

Участник
Сообщения
12,973
Реакции
10,927
  • Команда форума
  • #12
Результаты тестов за последние 2 дня - впечатляют.
Когда у нас есть вполне отлично работающее ядро (дополнительные финтиплюшки в планах кое-какие есть), можно начать работу над вебом.

Тут хотелось бы оговорочку сделать.
У нас есть два варианта пути, по которому мы можем пойти. У каждого свои плюсы, и минусы.
  1. Забить большой и жирный на старые версии PHP. Позволит не заморачиваться на тему "велосипедов" из-за неработающего функционала, и (в теории) реализовать веб в максимально кратчайшие сроки за счёт использования сторонних готовых библиотек.
    Под старыми версиями лично я подразумеваю все версии ниже 7.1 (срок её поддержки заканчивается уже в этому году, 01.12.2019). Т.е. минималка - 7.2.
  2. Оставить поддержку древнего поколения. Возможно, придётся кое-где соорудить "велосипеды" или прибегнуть к старым версиям библиотек (нет никаких гарантий, что в старых версиях не было каких-либо проблем с безопасностью, которые исправили в свежих).
    Минималка будет лояльнее (PHP 5.5).
Ещё хотелось бы обсудить тему фронта (шаблоны будут, но что именно требуется в них - пока неясно), но пока рано. Возможно, в другой раз.
Оффтоп
 

The_C@t

Слабоумие и отвага
Сообщения
272
Реакции
142
Я за 1 вариант.
Шаблоны нахрен не нужны - кому надо сам поправит. Минимализм.
Простой, надёжный функционал с поиском по демкам (Время, карта, стим)
 

Kruzya

Участник
Сообщения
12,973
Реакции
10,927
  • Команда форума
  • #16
Вчера начал работу над вебом. Пока лишь подгрузил пару пакетов, которые подходят для 5.5+, ибо не было на тот момент ясно, какому варианту отдадут предпочтение.
Заодно узнал, что оказывается репозиторий для веба я завёл ещё год назад, когда вся идея с демками была только в мыслях:
1565184819312.png

Сегодня чутка расширил плагин: добавил возможность сторонним плагинам "обрабатывать" попытку добавить событие сторонними плагинами. Это необходимо было для полноценной поддержки события смерти игрока в TF2 (в отличие от других игр, в TF2 может быть "искусственная смерть"; игрок умер, но только для игроков другой команды). Возможно, ещё где-то пригодится.
C++:
/**
 * AutoDemo Recorder - Team Fortress 2
 * Copyright (C) 2019 CrazyHackGUT aka Kruzya
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/
 */

#include <sourcemod>
#include <events>

#include <AutoDemo>
#include <tf2_stocks>

public Plugin myinfo = {
    description = "Extend event handling for Team Fortress 2",
    version     = "1.0",
    author      = "CrazyHackGUT",
    name        = "[AutoDemo] Team Fortress 2",
    url         = "https://kruzya.me"
};

public APLRes AskPluginLoad2(Handle hMySelf, bool bLate, char[] szBuffer, int iBuffer)
{
    if (GetEngineVersion() != Engine_TF2)
    {
        strcopy(szBuffer, iBuffer, "This plugin targeted only in Team Fortress 2!");
        return APLRes_Failure;
    }

    return APLRes_Success;
}

public void OnAllPluginsLoaded()
{
    DemoRec_AddEventListener("Core:PlayerDeath", HandleFakeDeaths);
}

public void OnPluginEnd()
{
    DemoRec_RemoveEventListener("Core:PlayerDeath", HandleFakeDeaths);
}

public bool HandleFakeDeaths(char[] szEventName, int iBufferLength, StringMap hEventDetails, Event &hEvent)
{
    // Write event if this death is not fake.
    return !(
        hEvent.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER
    );
}

Есть идея добавить ещё дополнительные поля для самой демо-записи, а не только игроков и событий. Но пока лишь где-то в мыслях.
Первая версия веба будет простой, с возможностью лишь скачивать демки, с поиском по карте и игрокам (и, возможно, встроенной авторизацией через Steam). В планах закончить эту самую первую версию до конца августа, но пока непонятно, как со временем будет.

P.S.: Создавая эту тему 4 дня назад, я слегка не ожидал, что желающих будет больше двух человек. Очень приятно, что это кому-то нужно.
 

Ju4qa

Участник
Сообщения
280
Реакции
65
Вчера начал работу над вебом. Пока лишь подгрузил пару пакетов, которые подходят для 5.5+, ибо не было на тот момент ясно, какому варианту отдадут предпочтение.
Заодно узнал, что оказывается репозиторий для веба я завёл ещё год назад, когда вся идея с демками была только в мыслях:
Посмотреть вложение 44390

Сегодня чутка расширил плагин: добавил возможность сторонним плагинам "обрабатывать" попытку добавить событие сторонними плагинами. Это необходимо было для полноценной поддержки события смерти игрока в TF2 (в отличие от других игр, в TF2 может быть "искусственная смерть"; игрок умер, но только для игроков другой команды). Возможно, ещё где-то пригодится.
C++:
/**
* AutoDemo Recorder - Team Fortress 2
* Copyright (C) 2019 CrazyHackGUT aka Kruzya
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see http://www.gnu.org/licenses/
*/

#include <sourcemod>
#include <events>

#include <AutoDemo>
#include <tf2_stocks>

public Plugin myinfo = {
    description = "Extend event handling for Team Fortress 2",
    version     = "1.0",
    author      = "CrazyHackGUT",
    name        = "[AutoDemo] Team Fortress 2",
    url         = "https://kruzya.me"
};

public APLRes AskPluginLoad2(Handle hMySelf, bool bLate, char[] szBuffer, int iBuffer)
{
    if (GetEngineVersion() != Engine_TF2)
    {
        strcopy(szBuffer, iBuffer, "This plugin targeted only in Team Fortress 2!");
        return APLRes_Failure;
    }

    return APLRes_Success;
}

public void OnAllPluginsLoaded()
{
    DemoRec_AddEventListener("Core:PlayerDeath", HandleFakeDeaths);
}

public void OnPluginEnd()
{
    DemoRec_RemoveEventListener("Core:PlayerDeath", HandleFakeDeaths);
}

public bool HandleFakeDeaths(char[] szEventName, int iBufferLength, StringMap hEventDetails, Event &hEvent)
{
    // Write event if this death is not fake.
    return !(
        hEvent.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER
    );
}

Есть идея добавить ещё дополнительные поля для самой демо-записи, а не только игроков и событий. Но пока лишь где-то в мыслях.
Первая версия веба будет простой, с возможностью лишь скачивать демки, с поиском по карте и игрокам (и, возможно, встроенной авторизацией через Steam). В планах закончить эту самую первую версию до конца августа, но пока непонятно, как со временем будет.

P.S.: Создавая эту тему 4 дня назад, я слегка не ожидал, что желающих будет больше двух человек. Очень приятно, что это кому-то нужно.
готов отдать себя, 2 сервера и веб для любых тестов
 

megoRU

Участник
Сообщения
383
Реакции
85
Если нужен тест веба я помогу.
 

Kruzya

Участник
Сообщения
12,973
Реакции
10,927
  • Команда форума
  • #19
Продолжаем работать над вебом.
Пока работа идёт медленно. Проектируем базово энтити и т.д.. А ещё нужно определиться с архитектурой самого сайта. Пока пришёл к следующей структуре файлов:
44570

  • internal. Папка с некоторыми внутренностями сайта.
    • addOns. Об этом чуть позже, но здесь будут лежать описания аддонов для движка в XML-формате.
    • runtime. Папка с временными файлами, которые будут формироваться сайтом во время тех или иных операций.
    • templates. Шаблоны.
  • src. Собственно, весь основной PHP-код сайта.
    • AutoDemo. Весь код проекта. Всё-всё-всё - здесь.
    • vendor. Сторонние используемые пакеты.
    • AutoDemo.php. Класс-фасад. Хранит текущее приложение (консольное/веб).
    • composer.json. Содержит перечисление всех используемых сторонних пакетов и описание того, как автозагрузчику нужно загружать файлы самого сайта.
    • composer.lock. Тоже самое, что и выше, но формируется всегда автоматически на основе composer.json самого сайта и аналогичных файлов из пакетов. Так же содержит перечисление всех точных версий используемых пакетов.
  • cmd.php. Входная точка для консоли. Пониже - чуть подробнее.
  • index.php. Входная точка для браузера. Именно этот скрипт будет принимать все запросы из браузера, и формировать ответы.
Как можно уже догадаться, чуть позднее будет возможно писать свои дополнения к веб-панели. Вёрстки пока нет от слова совсем, потому и папок для JS-скриптов и стилей нет.
 

Kruzya

Участник
Сообщения
12,973
Реакции
10,927
  • Команда форума
  • #20
44579
Выше мельком упомянул "входную точку для консоли".
Если без особого вдавания в подробности, то часть операций можно будет производить из консоли. Например, производить установку самого скрипта (полезно при установке на нескольких проектах). Ну и ещё по мелочи.
Но есть одна операция, которую можно будет делать только из консоли. Это производить выгрузку демо-файлов с серверов на веб.
В отличие от веб-запроса, консольные запуски PHP могут работать продолжительное время, и на них не распространяется стандартная настройка самого интерпретатора max_execution_time. Потому считаю, что это наиболее идеальное место для выполнения таких продолжительных задач.
На множестве хостов, где я тыкался, уже давно можно выполнять произвольные команды в кроне, потому пока не наблюдается никаких потенциальных проблем. Если Вы сидите на таком хосте, где такое недоступно - могу посоветовать лишь сменить хостинг. Пока никаких планов по поддержке автоматизированных выгрузок демок с серверов по запросу на скрипт не планируется.
Могу посоветовать BeGet как один из хостов, где можно делать такое:
44580


Все команды хранятся в /src/AutoDemo/Commands/. Используется компонент Symfony/Console для реализации консоли. В следствие, реализация любой консольной команды - пустяковое дело.
Как можно было догадаться по папке addOns в internal/, будет кое-какая возможность расширять функционал панели. В этой папке будут храниться описания каждого аддона. Для удобства реализации, там же хранится описание всех мета-объектов самой веб-панели.
XML:
<?xml version="1.0" encoding="utf-8" ?>
<addon id="AutoDemo" name="[AutoDemo] Web" version="0.0.0.2" internalVersion="12" author="CrazyHackGUT aka Kruzya">
    <files>
        <file path="index.php" />
        <file path="cmd.php" />
        <file path="src/AutoDemo.php" />
        <file path="src/composer.json" />
        <file path="src/composer.lock" />
        <dir  path="src/AutoDemo" />\
    </files>

    <commands>
        <!-- Nope. This is core, so all commands hard-coded in <code>src/AutoDemo/Console.php</code>. -->
    </commands>

    <templates>
        <template name="PAGE_CONTAINER" />
    </templates>

    <template_hooks />

    <phrases>
        <!-- Not implemented yet. -->
    </phrases>

    <hashes>
        <hash file="index.php" algo="md5" value="4DBBF505143A91F76A69AFA4B5434384" />
        <hash file="cmd.php" algo="md5" value="6BCCED9F47F30E3073394294B9C807D6" />
        <hash file="src/composer.json" algo="md5" value="390DD74B0EA004874AF1A7B13A8176DD" />
        <hash file="src/composer.lock" algo="md5" value="A922C2B1AAF9A28ED5621267C342B54A" />
        <!-- ... -->
    </hashes>

    <entities>
        <entity name="AddOn" />
        <entity name="Server" />
        <entity name="Player" />
        <entity name="DemoRecord" />
        <entity name="RecordedEvent" />
        <entity name="RecordedPlayer" />
    </entities>

    <events>
        <event name="user_logged" data="AutoDemo\Event\UserLogged" />
        <event name="user_logout" data="AutoDemo\Event\UserLogout" />
        <event name="demo_upload_pre" data="AutoDemo\Event\PreUpload" />
        <event name="demo_upload_post" data="AutoDemo\Event\PostUpload" />
        <event name="event_output" data="AutoDemo\Event\EventOutput" />
    </events>
</addon>
 
Сверху Снизу