Общие модули
Модуль — пакет на 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 — модуль проекта: он работает в этом
плагине, а остальные вызывают его там
(свой модуль).
HTTP
Плагин обращается к веб-сервисам через две функции, обе глобальные — без строки импорта:
Свой модуль
Модуль — код на TypeScript, которым пользуются плагины проекта, — greeter.greet(player), без строки импорта, по имени, которое модуль даёт себе сам. То, что он экспортирует, — функции и типы — и есть его API. Модуль говорит о себе через defineModule, проект перечисляет его в amxts.config.ts, а на сервере работает один его экземпляр. menu-core и config-core — модули. Код без своего состояния на сервере — клиент какого-то сервиса, помощники — может быть и библиотекой: она компилируется в каждый плагин, который её импортирует.