Пользовательские плагины в JavaScript играх

5de93f862ba8475abfd51ecfd752708b.jpgКто не знает, Wargaming сейчас разрабатывает тактическую карточную игру WoT: Generals. Web-версия написана на JS, используются LibCanvas и AtomJS. Я принимал непосредственное участие и хочу рассказать про функционал, который мне кажется интересным и может быть полезным во всех веб-играх. А именно система плагинов игры, которая вдохновлялась пакетными менеджерами в Линуксе и имеет следующие возможности:

— История изменений плагинов
— Автоматическое обновление плагина при обновлении версии игры
— Разработка плагинов на localhost
— Неограниченное количество веток, например для нестабильных версий
— Зависимости (плагин А автоматически подключает плагин Б)
— Следствие предыдущего пункта — встроенная возможность делать паки
— Легкое изменение любой части клиента игры
— Полный административный контроль авторов игры над всеми плагинами
— Поиск по базе плагинов

При этом простая установка юзером и удобная работа для плагинописцев.

Игра


Генералы — это тактическая карточная игра. Думаю, многие знакомы с Magic The Gathering. Основной геймплей — это поле боя 5×3, где задача — карточками танков, взводов и приказов уничтожить штаб противника.

26db0762ca244f9dab15b5d1c738d9ef.jpg

Вне боя есть такие экраны как Ангар для выбора колоды, с которой вы пойдёте в бой, дерево исследований, редактор колод и так далее.

748982106f864adaaf75e92f2b12a535.jpg

Объекты, где множество анимаций вроде карусели в ангаре, дерева исследований и, конечно, боя написаны на LibCanvas и отрисовываются на html5 canvas. Интерфейсы попроще пишутся на html.

Про систему плагинов


Плагины — однозначно полезный функционал.

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

И хотя официального релиза у нас ещё не было, но система плагинов работает у нас уже полтора года. Я долго думал над тем, какими они должны быть. И ниже я расскажу историю как и к чему мы пришли в итоге.

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

К счастью, это довольно просто по двум причинам:

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

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

Имеется три способа изменения поведения игры:

1. Использование ограниченного API


При создании плагина ему передается инстанс объекта Wotg.Plugins.Simple с базовыми методы, которые позволяют самые простые операции — подмена картинок, смещение элементов, изменение звуков и т.д. Такое используется для простых плагинов:

5c04263dfa8c415e9c95799b55838a55.jpg

2. Подписка на события


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

3. Агрессивное изменение


Это самый сложный, но и самый глубокий метод (если вы понимаете о чём я ;)). Он позволяет изменить любой метод любого класса с возможностью вызова предыдущего варианта. Примерно в коде это выглядит так (часть плагина, которая позволяет сохранять реплеи на стороннем сервере). В данном случае изменяется метод save у ReplayManager.

plugin.refactor( Wotg.Utils.ReplayManager, {

        save: function method (battle) {
                method.previous.call(this, battle);
                
                var replay, xhr;
                
                replay = this.getCompiledDataFrom(battle);

                xhr = new XMLHttpRequest();
                xhr.open("POST", MY_OWN_REPLAYS_SERVER, true);
                xhr.send(
                        "player=" + replay.player.name +
                        "&opponent=" + replay.opponent.name +
                        "&replay=" + JSON.stringify(replay)
                );
        }
        
});



Это дает возможность изменить любое поведение на любое другое поведение вплоть до написания нового функционала, как внутриигровой генератор карт:

d6cc62c2fd984d38b358331bd1298524.jpg

Вот такая она с точки зрения кода плагина. Но как все это организовать в плане подключения?

История


Изначально (ещё пару лет назад) было решение использовать встроенные в браузер аддоны и официальные магазины вроде chrome.google.com/webstore и addons.mozilla.org/uk/firefox. Но с этим были проблемы. Такие плагины было тяжело разрабатывать, пока игра не зарелизилась — их нельзя добавить в магазин и пользователям приходилось копипастом кода их добавлять с темы на форуме игры Использование unsafeWindow вместо классического window вносило путаницу для разработчиков. А потом с unsafeWindow вообще появились дополнительные запреты. В общем — темные времена и стало ясно, что необходимо куда-нибудь двигаться.

Хотелось чего-то прогрессивного и удобного. И потому было решение использовать GitHub. Один репозиторий на коммиты и пул-реквесты в репозиторий игры. Удобность работы выросла значительно, но были две проблемы — очень долгое обновление GitHub Pages и отсуствие возможности удобного администрирования. Зато было очевидно, что направление движения правильное. И уже было понятно, где наша Земля Обетованная.

GitLab


Мы подняли GitLab на нашем сервере и выделили его полностью под плагины. И это было божественно. Схема следующая:

— Есть пользователи GitLab — наши плагинописцы
— У каждого пользователя есть репозитории — по одному на каждый плагин
— Репозитории могут иметь несколько бранчей. Например, master и unstable. Master включается по-умолчанию.

В итоге мы получаем следующие возможности:

— Самое главное — весь репозиторий находится под нашим административным контролем и имеет общий с нашим сервером Uptime.
— Формат имени Owner:Title:Branch. Например Shock:MyCoolPlugin — основной плагин, а Shock:MyCoolPlugin:Unstable — версия для разработки, которая мерджится в мастер по факту полной готовности. Путь к файлу определяется шаблоном https://gen-git.socapp.net/{author}/{title}/raw/{branch}/{title}.js. Это дополнительно облегчает совместную работу над плагинами — один разработчик ответвляет от репозитория другого, получает плагин с таким же именем, но другим автором, вносит свои изменения, может даже дать установить свой плагин другим пользователям, а потом создает пул-реквест в основной плагин.
— При установке информация о плагине записывается в localStorage и его основной файл подключается каждый раз после загрузки всех классов, но до вызова точки входа.
— Каждый плагин имеет версию игры для которой он предназначен и при выходе обновления автоматически выключается пока автор не изменит версию в соответствующей ветке и тогда плагин снова автоматически включится для всех пользователей, у которых он установлен. При этом следующую версию можно заранее подготовить на супертесте и просто во время выхода обновления вмерджить её через веб-интерфейс одной кнопкой.
— В коде плагина достаточно написать require и автоматически подтянутся необходимые плагины (зависимости). Они подтянутся в корректном порядке и будут доступны из тела плагина как показано ниже.
— Так же можно при помощи include включать дополнительные классы с плагина. Приблизительно так:

new Wotg.Plugins.Simple({
        version: '0.6.0',
        require: [ 'Another:Plugin' ],
        include: [ 'AnotherClass' ],
}, function (plugin, events, required) {
        console.log( required['Another:Plugin'] ); // Ссылка на необходимый плагин
        console.log( plugin.included['AnotherClass'] ); // Ссылка на необходимый класс
});


— При помощи команды plugin.addStyles( 'my.css' ) подключить свои стили.
— Каждый плагин имеет свои собственные настройки, которые можно получить командой plugins.getConfig( 'index' ).
— Самые простые плагины можно изменять через веб-интерфейс GitLab, а более сложные благодаря git clone разрабатывать локально. Для этого достаточно по шаблону сконфигурировать nginx и благодаря настройке в игре соответствующая директория станет использоваться как репозиторий плагинов. И тогда любое изменение в этой директории подключенного плагина будет отображаться без коммитов в игре разработчика.
— У GitLab есть API. Был зарегистрирован фейковый пользователь и благодаря его приватному токену любой веб-клиент игры может отправлять запросы в API. Это позволяет сделать поиск плагинов, валидацию названий и т.п.

# plugins add Test:CardCreate
No such plugin. Did you mean:
  - Shock:CardCreate 

# plugins add Test:Ca
Min plugin title length is 4: Test:Ca

# plugins add Test:Card
No such plugin. Did you mean:
  - Shock:CardCreate 

# plugins add Test:Exa
Min plugin title length is 4: Test:Exa

# plugins add Test:Exam
No such plugin. Did you mean:
  - Shock:Example 
  - Isk1n:Example

# plugins add Shock:Unknown
No such plugin, I dont know, what you want to install

# plugins add Shock:Example:Test213
No such plugin. Did you mean:
  - Shock:Example:master 
  - Shock:Example:new-test


Console vs GUI


На данный момент всё управление и установка плагинов производится через внутриигровую консоль, которая открывается по Ctrl ~. Всем достаточно удобно (хотя иногда пользователи спрашивают, где тильда). Но есть далекие планы и возможности создать GUI — из localStorage получаем список установленных плагинов, а благодаря GitLab API реализуем их поиск и отображение информации о них.

Если кому интересно — можно почитать (на форуме необходимо авторизироваться) документацию для разработчиков, посмотреть раздел плагинов, или посмотреть как всё это выглядит в WoT: Generals.

Я специально сюда не включил реализацию именно в плане кода, т.к. она довольно проста. Как на меня, самое интересное — это именно использование GitLab как пакетного менеджера. Но если есть вопросы к технической реализации или к идее — жду в комментариях.

© Habrahabr.ru