Перейти к основному содержанию

Внедрение библиотек

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

Включение в компиляцию

Прежде чем библиотека сможет быть использована, ее нужно включить в build.config. Если свойство defaultConfig.libraryDir указывает на существующую папку, все библиотеки из этой папки будут автоматически скомпилированы.

Давайте рассмотрим это на примере библиотеки BlockEngine. Пусть, свойство defaultConfig.libraryDir = "library/", тогда мы должны расположить BlockEngine.js в папке library/. В этом случае библиотека будет загружена, это также приравнивается к добавлению объекта в свойство compile:

build.config
{
"compile": [
...
{
"path": "library/BlockEngine.js",
"sourceType": "library"
}
],
...
}
Библиотеки в презагрузчике

Свойство defaultConfig.libraryDir еще не загружено, так что просто используйте свойство compile для ручного включения библиотек в этот этап загрузки. Например, библиотеки Files и Environment в связке позволяют вам собирать карты для разных версий игры, используя простые конфигурации описания.

Углубимся в скрипт

И рассмотрим контекст библиотеки BlockEngine. Любая библиотека должна начинаться с метода LIBRARY, дальнейший код будет выполнен лишь когда к библиотеке будет совершено обращение из другого скрипта. Считайте эту функцию прерыванием, она объявляет метаданные библиотеки и завершает выполнение кода:

LIBRARY({
name: "Назови меня",
version: 1,
author: "ICDocs",
api: "CoreEngine",
shared: false,
dependencies: []
});

С названием, версией, авторством и API мы уже познакомились. Однако, версия здесь (в отличии от mod.info и лаунчера) — число. Причем, исключительно целочисленное. Начиная с 1, оно должно возрастать с каждым обновлением библиотеки. Так, спустя три версии, свойство должно быть определено как version: 3. И ни в коем случае не version: 1.2.

Свойство shared определяет является ли библиотека глобальной. Глобальные библиотеки загружаются для всех модов единожды, тогда как локальные содержат лишь уникальные данные для одного конкретного мода. Используйте глобальные библиотеки для создания, например, типов электроэнергии.

Массив dependencies используется для определения библиотек, которые должны быть загружены до этой библиотеки. Давайте прежде рассмотрим систему совместимости и экспорта.

Экспортируем

Метод EXPORT реализует функционал для объявления возможностей библиотеки. Любые значения, экспортируемые этим методом могут быть внедрены в скрипт из которого вызывается функция IMPORT. Давайте разберем базовый функционал этого метода:

EXPORT("свойство", значение);
EXPORT("свойство:версия", значение);

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

let someProperty = "Некое значение";
EXPORT("someProperty", someProperty);

let somePropertyToBeRenamed = true;
EXPORT("renamedProperty", somePropertyToBeRenamed);

let Language = {
en: "English",
ru: "Russian"
};
EXPORT("Language:2", Language);

EXPORT("SOME_CONSTANT", Math.PI * 2);

Свойства не обязаны ссылаться на существующие значения в контексте или иметь те же названия что и у значений, как это сделано в EXPORT("someProperty", someProperty). Однако, это является хорошим тоном, тем кто использует вашу библиотеку будет проще разобраться в вашем коде.

Объект Language здесь будет доступен лишь при использовании библиотеки до версии 2 включительно. Указание версии создано в первую очередь для добавления обратной совместимости, следующий блок раскрывает ее подробности.

Использование констант

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

Система обратной совместимости

Основана на разделении импортируемых и экспортируемых библиотекой контекстов по версиям. Рассмотрим это на примере некой библиотеки Library:

Library.js
LIBRARY({
name: "Library",
version: 1,
api: CoreEngine,
shared: true
});

EXPORT("doSomething", function() {
Logger.Log("Что-то произошло");
});

Изначально у нас был лишь один метод doSomething, после мы решили расширить возможности новым методом deprecatedMethod. Однако с обновлением до версии 3, мы поняли что метод doSomething нужно глобально переосмыслить, а добавленный в прошлой версии deprecatedMethod и вовсе больше не требуется.

Эта система позволяет нам обновлять возможности библиотеки, не лишаясь поддержки старого функционала. Пожалуйста, не забывайте об этом и никогда не удаляйте или глобально не изменяйте методы, которые однажды уже были реализованы. Единственный допустимый случай — локальные библиотеки, так как они уникальны для каждого мода.

Зависимости от других библиотек

Свойство dependencies в заголовке вашей библиотеки, как и было сказано, запускает библиотеку лишь в случае если нужные библиотеки загружены. Они приводятся в формате "название:версия" или просто "название" для актуальной версии библиотеки. К примеру, ModBrowser.Query требует Connectivity для реализации ее методов:

ModBrowser.Query.js
LIBRARY({
name: "ModBrowser.Query",
...
dependencies: ["Connectivity:1"]
});
IMPORT("Connectivity:1");

ModBrowser = {};
ModBrowser.Query = function () {
this.query = {};
};
ModBrowser.Query.prototype = new Connectivity.Reader;
...

И как вы могли заметить, библиотеку Connectivity нужно импортировать. Зависимости лишь определяют библиотеки, которые должны быть загружены, но не импортируют их.

Перейдем к внедрению

Для этого в контексте скриптов мода существует метод IMPORT. Он производит поиск библиотек в репозитории текущего мода и обращается к глобальному, только если в локальном ничего не нашлось. Например, импортируем ToolType из библиотеки BlockEngine версии 8:

IMPORT("BlockEngine:8", "ToolType");

Или все пространство библиотеки BlockEngine последней версии:

IMPORT("BlockEngine");

Последний код фактически приравнивается к IMPORT("BlockEngine", "*"), то есть импортировать все. Специальных выборок объектов для этого метода не предусмотрено.

Теперь мы напрямую из кода можем обращаться к значениям:

Logger.Log("PICKAXE: " + JSON.stringify(
ToolType.PICKAXE
), "ToolType");

Как версии влияют на импорт

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

Если мы не определяем конкретную версию для импорта библиотеки, будет использована последняя доступная. Последней локально доступной будет библиотека в текущем моде, глобальная же может находиться совершенно в любом месте.

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

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