Модули

Общие модули

Модуль — пакет на TypeScript, которым пользуются плагины: официальные menu-core и config-core или ваш собственный. У модуля один экземпляр на сервер, общий для всех плагинов, которые им пользуются:

menus
.
addCondition
("IS_ALIVE", (
player
) =>
player
.
isAlive
);
menus
.
addAction
("RESET_SCORE", ({
player
}) => resetScore(
player
));
server
.
addCommand
("/menu", ({
player
}) =>
menus
.
show
(
player
, "MAIN_MENU"));
function resetScore(
player
: Player) {
player
.
frags
= 0;
player
.
deaths
= 0;
}

Условие и действие, которые добавил этот плагин, принадлежат меню, какой бы плагин его ни показал; у игрока одно открытое меню, а не по одному на плагин; папка конфигов, которую задал один плагин, — папка для всех. Код выглядит так же, как вызов внутри одного плагина, и типы в редакторе те же.

Модуль указывается в amxts.config.ts проекта (modules: ["@amxts/menu-core"] или npx amxts module add menu-core), и плагины пользуются им по имени, которое он даёт, — menus — без импорта (автоимпорты) или импортируют его по имени пакета. Модуль, которым не пользуется ни один плагин, не собирается.

Где работает модуль

Модуль работает в собственном плагине, который создаёт сборка, называет по пакету — menu-core.aot для @amxts/menu-core — и вписывает в plugins.ini перед вашими плагинами, после модулей, которые нужны ему самому. Когда ваш плагин вызывает модуль, вызов выполняется там, а ответ возвращается обратно.

Модуль готов, как только загрузился его плагин. Плагин, записанный в plugins.ini после него, — а сборка так и записывает все плагины, — может вызывать его прямо с верхнего уровня файла. Если плагин модуля не работает, вызов пишет в консоль сервера одну строку — menu-core: no plugin runs it - is menu-core.aot in plugins.ini? — и ничего не возвращает: false, 0, "" или null.

Когда один из ваших плагинов выгружают или перезагружают — amxts dev перезагружает плагин, который вы сохранили, — модуль убирает то, что этот плагин ему дал: menu-core — созданные им меню и добавленные пункты, resemiclip — его правило. Новая загрузка даёт их заново со своего верхнего уровня, поэтому меню, созданное там, оказывается в модуле один раз. У игрока, у которого это меню открыто, оно остаётся открытым при перезагрузке: его рисует заново новая загрузка, на той же странице; после выгрузки оно закрывается. Ваш собственный модуль делает то же самое через onPluginStop.

Вызов в другой плагин
  • Вызов копирует аргументы в обе стороны. Он обходится намного дороже вызова внутри плагина: при запуске и на команду — нормально, для каждого игрока каждый кадр — лучше не надо.
  • Собирайте вместе. Плагин модуля и плагины, которые им пользуются, собираются из одной версии модуля; amxts build и amxts dev так и делают. Плагин, собранный с другой версией, вместо вызова получит строку в консоли.

Что передаётся между плагинами

В модулеМежду плагинами
number, boolean, string, юнион строккак есть
T[], T | nullкак есть
Playerтот же игрок
объект, который хранит модуль, — он его отдаёт и принимает обратно, у объекта есть свои методы (Menu, ConfigNode) или модуль его экспортирует (export const semiclip = new Semiclip())собственный объект модуля: его поля и аксессоры читаются и пишутся в модуле — сеттер выполняется там же, — и методы выполняются там же
любой другой объект — интерфейс (MenuShowOptions), класс из полейкопия; незаданное поле так и остаётся незаданным
объект с методами, переданный в колбэк (MenuEvent)копия, чьи поля после вызова возвращаются обратно: event.preventDefault() работает
функция (условие, onSelect)остаётся в плагине, где написана; модуль вызывает её там

Объект, который хранит модуль, — один и тот же объект во всех плагинах: menus.find("SHOP") дважды даёт один и тот же Menu, menu.title = "..." меняет меню модуля, а функция, переданная в его метод, вызывается в том плагине, где её написали. Функция, переданная дважды, — одна и та же функция, так что модуль видит, что она уже зарегистрирована.

Что не передаётся

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

  • параметр или результат типа, который не передаётся, — Map, Set, обобщённый класс, класс из стандартной библиотеки, тип, который модуль не экспортирует;
  • значение по умолчанию, которое другой плагин не может вычислить сам, — index = base; литерал, {}, [] или null подходят;
  • обобщённая или async-функция или остаточный параметр (...args);
  • экспортированная переменная (export let count): экспортируйте функцию, которая её читает или задаёт, или объект класса самого модуля (export const counter = new Counter()), чьи аксессоры это делают.
~/modules/bad: export function remember - parameter "counts": Map from the standard library cannot cross - pass its contents as an array or a record

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

Ограничения

  • Поле-массив или поле-объект общего объекта читается как копия.menu.items.push(item) меняет копию. Пользуйтесь методом самого модуля (menu.addItem(...)) или присваивайте поле целиком.

Файл общего кода в папке плагинов (plugins/lib/format.ts, импорт ~/lib/format) — не модуль: он компилируется в каждый плагин, который его импортирует, и переменные у каждого свои. А файл plugins/modules/<name>.ts рядом с плагином plugins/<name>.ts — модуль проекта: он работает в этом плагине, а остальные вызывают его там (свой модуль).