Основы

Плагин: команды, события, таймеры, чат, переводы

Плагин — это один файл .ts в папке плагинов проекта. Работу он делает на верхнем уровне файла: называет себя, добавляет команды и подписывается на события. То, чем он пользуется из ядра, — plugin, server, Player, print — строки импорта не требует (автоимпорты).

plugin
({
name
: "Welcome",
version
: "1.0.0",
author
: "you" });
server
.
addCommand
("/hp", ({
player
}) => showHp(
player
));
server
.
addEventListener
("putInServer", (
event
) => {
print
(
event
.
player
, "Добро пожаловать на сервер!");
}); function showHp(
player
: Player) {
print
(
player
, `${
player
.
name
}, у тебя ${
player
.
health
} HP`);
}

Всё, что есть на этой странице, — API ядра, @amxts/core, и оно импортируется само.

Автоимпорты

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

semiclip
.
rule
= (
player
,
target
) =>
player
.
team
==
target
.
team
;
server
.
addCommand
("/through", ({
player
}) => showThrough(
player
));
function showThrough(
player
: Player) {
const
names
=
semiclip
.
passesThrough
(
player
).
map
(
other
=>
other
.
name
);
print
(
player
, `Вы проходите сквозь: ${
names
.
join
(", ")}`);
}

server, Player и print — ядра; semiclip даёт @amxts/resemiclip в проекте, где amxts.config.ts его перечисляет. Импорты встают после последней строки файла, поэтому ошибка по-прежнему указывает на строку, которую вы написали.

  • Что импортируется само: всё, что @amxts/core экспортирует для плагинов, — Player, Entity, server, game, print, lang, effects, Cvar, Forward, Storage, таймеры, типы событий (TakeDamageEvent), имена полей и флагов (Team, HideHud) — и имя каждого модуля (menus, configs, semiclip). Не нативы и константы (@amxts/core/natives, @amxts/core/constants), не файлы (@amxts/core/fs), не ваши собственные файлы: плагин, которому они нужны, говорит об этом импортом.
  • Своё имя важнее. Переменная, параметр или функция с именем server — ваши там, где они видны; автоимпорт — только для мест, где их не видно.
  • Явный импорт тоже работает: import { Player } from "@amxts/core", import * as menus from "@amxts/menu-core". Его пишет плагин, которому модуль нужен под другим именем, и для того, что само не импортируется.
  • Что называет импорт. API ядра — по имени пакета: @amxts/core, @amxts/core/natives, @amxts/core/constants, @amxts/core/fs, @amxts/core/os; модуль — по имени его пакета; ваши собственные файлы — через ~/, это папка плагинов: import { twice } from "~/lib/math" — это plugins/lib/math.ts, из любой подпапки. ~/ называет только ваши файлы: импорт API ядра через него не собирается.
  • Редактор знает те же имена: npx amxts prepare — его первым шагом запускают dev, build, typecheck и npm install — пишет .amxts/imports.d.ts, где они глобальные, поэтому автодополнение, переход к определению и проверка типов работают без импорта. Файл пишется заново, когда после изменения модулей в amxts.config.ts запускается команда.
  • Как выключить: imports: { autoImport: false } в amxts.config.ts; тогда каждый плагин импортирует то, чем пользуется, сам.
// amxts.config.ts
export default 
defineConfig
({
modules
: ["@amxts/menu-core"],
imports
: {
autoImport
: false },
});

Модуль говорит, что даёт, в своём defineModule, — себя пространством имён, imports: [{ from: "@amxts/menu-core", as: "menus" }], или один из своих экспортов под его именем, [{ from: "@amxts/resemiclip", name: "semiclip" }] (свой модуль). Два источника одного имени останавливают сборку, и она называет оба.

Собирается только то, чем пользуются

Плагин получает импорт только того, чем пользуется, поэтому модуль, которым не пользуется ни один плагин, не попадает ни в один из них. Сборка идёт дальше: собственный плагин модуля она собирает и пишет в plugins.ini, только когда модулем пользуется хоть один плагин проекта — через автоимпорт или явный импорт, сам или через файл, который он импортирует. Модуль из amxts.config.ts, которым не пользуется никто, в сборку не попадает, и сборка об этом говорит:

i module "resemiclip" is installed but no plugin uses it - left out

Модулем, чьи нативы вызывают Pawn-плагины, — mc_* у menu-core, cfg_* у config-core — пользуются плагины, которых сборка не видит. Его оставляет pawn:

// amxts.config.ts
export default 
defineConfig
({
modules
: ["@amxts/config-core", "@amxts/menu-core"],
pawn
: ["@amxts/menu-core"], // Pawn-плагины вызывают mc_*: собирается, даже если им не пользуется ни один плагин
});

То, что модуль требует, идёт вместе с ним. В amxts dev сохранение, после которого плагин начал пользоваться модулем, собирает модуль вместе с плагином; модуль, которым больше не пользуется ни один плагин, уходит из plugins.ini.

Описание плагина

plugin
({
name
: "Welcome",
version
: "1.0.0",
author
: "you",
description
: "Приветствует игроков" });

Имя, версия и автор, под которыми сервер показывает плагин: их выводит amxts_plugins в консоли сервера. description можно не писать. plugin вызывается один раз, в начале файла.

Команды с v0.2

interface KickArgs {
    
target
: Player;
reason
?: string;
}
server
.
addCommand
<KickArgs>("/kick <target> [reason]", ({
player
,
target
,
reason
}) => {
print
(0, `${
player
.
name
} кикает ${
target
.
name
}`);
target
.
kick
(
reason
?? "Кикнут админом");
}, {
access
: "kick",
description
: "Кикнуть игрока" });
server
.
addCommand
("/hp", ({
player
}) =>
print
(
player
, `${
player
.
health
} HP`));
server
.
addCommand
("myplugin_heal <amount>", ({
player
,
amount
}) =>
print
(
player
, `лечение ${
amount
}`)); // без слэша - команда консоли
server
.
addCommand
("say rules", ({
player
}) => showRules(
player
)); // строка чата без слэша

Первый аргумент — использование команды: её имя, затем аргументы, <name> — тот, что игрок должен набрать, [name] — тот, что может пропустить. Обработчик получает их под этими именами, вместе с player — игроком, который набрал команду.

Типы аргументов — интерфейс, с именем, переданный первым, как addCommand<KickArgs> (тип, написанный на месте, <{ ... }>, читается хуже и не годится второй раз). Поле говорит, как читается его слово:

типсловонапример
stringкак есть; последний аргумент забирает остаток строкиreason в /kick bob being rude — "being rude"
numberчисло/give 5
Player#userid, имя целиком или его часть, в любом регистре/kick bo, /kick #12
"on" | "off"одно из этих слов/mode on

[name] в использовании — это name?: в интерфейсе, а <name> — поле без ?. Имя, которое есть в использовании, но нет в интерфейсе, — или наоборот, — не собирается, и ошибка говорит какое. Без интерфейса каждый аргумент — string: ({ amount }) выше — текст.

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

  • Имя со / — команда чата. Её запускают и say /hp, и say_team /hp, а сама строка в чате не появляется.
  • Имя без / — команда консоли: игрок набирает myplugin_heal 50 у себя в консоли.
  • "say <фраза>" срабатывает, когда игрок пишет в чат ровно эту фразу: "say rules" — на rules, но не на rules please. Аргументов у неё нет.

Регистр в имени команды не важен: /HP запускает /hp.

access — право админа, которое нужно игроку, как его даёт users.ini: "kick", "ban", "slay", "map", "cvar", "rcon", "levelA" … "levelH" и остальные — редактор их подсказывает. У кого права нет, для того команда ничего не делает, а его строка уходит в чат обычным сообщением. description — то, что показывает amx_help.

Права самого игрока — player.access, массив тех же имён: player.access.includes("ban").

Команда помощи

server.commands перечисляет команды, которые добавил плагин, — у каждой usage, description и access, — это и печатает /help:

server
.
addCommand
("/help", ({
player
}) => {
for (const
command
of
server
.
commands
) {
if (
command
.
access
== null ||
player
.
access
.
includes
(
command
.
access
))
print
(
player
, `${
command
.
usage
} - ${
command
.
description
}`);
} });

Команды сервера

interface ResetArgs {
    
what
?: "scores" | "all";
}
server
.
addServerCommand
<ResetArgs>("myplugin_reset [what]", ({
what
}) => {
console
.
log
(`сброс: ${
what
?? "all"}`);
});

Команда консоли сервера: её набирают там, присылают по rcon или запускает другой плагин через server_cmd. Её использование и аргументы читаются так же, как у команды игрока; игрок её не набирает, поэтому player у обработчика нет. То, что он печатает через console.log, уходит тому, кто прислал команду, — по rcon это ответ rcon.

События сервера

server
.
addEventListener
("putInServer", (
event
) => {
print
(
event
.
player
, "Добро пожаловать!");
});
server
.
addEventListener
("disconnected", (
event
) => {
console
.
log
(`${
event
.
player
.
name
} вышел: ${
event
.reason}`);
});
server
.
addEventListener
("configsExecuted", loadSettings); // сервер выполнил свои конфиги

server.addEventListener работает как addEventListener в браузере: первый аргумент — имя события, второй — функция, которую сервер вызывает каждый раз, когда событие случается. От имени зависит тип event, поэтому (event) => ... пишется без аннотации, а редактор дополняет имена и подчёркивает опечатку. Поля типизированы: игрок — Player, текст — string, да/нет — boolean (event.dropped).

СобытиеКогда
"init"плагин запускается, когда загружены все плагины
"pluginsLoaded"все плагины запустились: время создать форвард, который услышат другие плагины
"configsExecuted"карта загрузилась, и сервер выполнил свои конфиги — server.cfg, amxx.cfg, конфиг карты: время читать квары
"precache"грузится карта: время прекешировать модели и звуки
"changeLevel"карта вот-вот сменится
"end"плагин останавливается: смена карты или выключение сервера
"connect"игрок подключается и ещё не в игре
"authorized"стал известен Steam ID игрока (event.steamId)
"putInServer"игрок вошёл в игру
"disconnected"игрок ушёл (event.dropped, event.reason)
"playerChange"изменилось поле, которое плагины добавили в Player (общие поля игрока)
"command"игрок отправил команду консоли
"impulse"игрок отправил импульс: 100 — фонарик, 201 — спрей
"suicide"игрок набрал kill в консоли
"frame"каждый кадр сервера

В "connect", "authorized" и "putInServer" игрок ещё не появился на карте, поэтому event.player — это Client: у него есть id, name, ip, steamId, access, team, muted и command, но нет здоровья и оружия. У Player есть всё, что есть у Client, поэтому функция, которой нужно только это, принимает Client и работает с обоими.

Весь список показывает редактор; его подсказка называет форвард AMX Mod X, который под событием (client_putinserver). То, что происходит с игроком в игре, — он «думает» каждый кадр, меняет свои данные, касается чего-то, — это игровые события: game.addEventListener("preThink", ...), "userInfoChange", "touch".

Обработчик, написанный отдельной функцией, указывает тип события — это имя форварда словами: ClientPutinserverEvent, ClientDisconnectedEvent, ClientImpulseEvent.

server
.
addEventListener
("impulse", onImpulse);
function onImpulse(
event
: ClientImpulseEvent) {
if (
event
.
impulse
== 100)
print
(
event
.
player
, "Фонарик!");
}

server.removeEventListener("impulse", onImpulse) отписывает обработчик: передайте ту же функцию, что добавляли.

Имя пишется строкой прямо в вызове: server.addEventListener(name, ...) с именем в переменной — ошибка сборки, потому что по имени выбирается тип события.

Обработчик — замыкание, как в JavaScript: он видит переменные функции, в которой написан, и они живут, пока живёт он.

server
.
addEventListener
("putInServer", (
event
) => {
const
player
=
event
.
player
;
setTimeout
(() =>
print
(
player
, `Рады видеть, ${
player
.
name
}!`), 2000);
});

Сообщения клиентам с v0.2

server
.
addMessageListener
("death", (
event
) => {
if (
event
.
headshot
)
print
(0, `${
event
.
killer
?.
name
} - headshot - ${
event
.
victim
?.
name
}`);
});

Любое сообщение, которое сервер шлёт клиенту, — строка чата, значок HUD, часы раунда, сообщение об убийстве, — слышно через server.addMessageListener до того, как оно ушло, с аргументами в типизированных полях. Подробности и имена всех сообщений — на странице Сообщения.

События игры

game
.
addEventListener
("newRound", () =>
console
.
log
("новый раунд"));
game
.
addEventListener
("takeDamage", (
event
) => {
if (
event
.
attacker
.
team
==
event
.
player
.
team
)
event
.
preventDefault
();
});

То, что происходит в игре, — урон, возрождения, смерти, конец раунда, покупки — это события на game, так же как события сервера — на server. Обработчик может остановить действие игры или ответить вместо неё. Их приносят модули ReAPI и Ham Sandwich, и большинство слышно и без ReAPI; подробно — в Событиях игры.

Таймеры

const 
handle
=
setTimeout
(() =>
print
(player, "Вперёд!"), 2000); // один раз, через 2 с
clearTimeout
(
handle
); // отменить до срабатывания
function countdown(
player
: Player) {
let
left
= 3;
const
ticking
=
setInterval
(() => {
print
(
player
, `${
left
}`);
left
--;
if (
left
== 0)
clearInterval
(
ticking
);
}, 1000); }

Как в браузере: setTimeout вызывает функцию один раз через заданное число миллисекунд, setInterval — раз в столько-то миллисекунд, и оба возвращают дескриптор, который принимают clearTimeout и clearInterval. Функция может читать и менять переменные вокруг себя. Функция без параметров передаётся как есть: setTimeout(startVote, 3000).

Внутри async-функции так же ждёт await sleep(1000) (async).

Не меньше 0,1 секунды
Как любой таймер AMX Mod X, таймер ждёт не меньше 0,1 секунды и срабатывает на кадре сервера: setTimeout(f, 10) выполнится примерно через 100 мс.

Время и случайность

Date
.
now
(); // миллисекунды с 1970 года
new
Date
().
getHours
(); // час по часам сервера
new
Date
().
getUTCHours
(); // час по UTC
const
start
= performance.now(); // миллисекунды с точностью до микросекунды, для замеров
Math
.
random
(); // 0 <= x < 1, при каждом запуске новая последовательность
game
.
time
; // часы игры: секунды с начала карты

Date работает как в JavaScript: new Date() — сейчас, геттеры UTC (getUTCHours, ...) и местные (getHours, getDate, getMonth, getFullYear, getTimezoneOffset, ...) — местные по часовому поясу сервера. Дата становится текстом так, как её пишет JavaScript, по часовому поясу сервера:

const 
now
= new
Date
();
now
.
toString
(); // "Tue Sep 29 2026 14:05:09 GMT+0300"
now
.
toLocaleString
(); // "9/29/2026, 2:05:09 PM"
now
.
toLocaleString
("ru-RU"); // "29.09.2026, 14:05:09"
now
.
toLocaleDateString
("de"); // "29.9.2026"
now
.
toLocaleTimeString
("en-GB"); // "14:05:09"

toLocaleString, toLocaleDateString и toLocaleTimeString принимают язык: en-US, если он не назван, en-GB, de, fr, ru и языки, которые пишутся как ru (uk, be, kk); другой пишется как en-US.

game.time — часы, по которым идут поля сущностей, хранящие момент: grenade.damageTime = game.time + 1. Таймеры атаки — nextPrimaryAttack оружия, nextAttack игрока — считаются от текущего момента: weapon.nextPrimaryAttack = 1 — это через секунду.

Чат и консоль

print
(
player
, "Это видишь только ты");
print
(0, "Это видят все");
print
(
player
, "Здоровье восстановлено!", "center"); // "chat" (по умолчанию), "center", "console", "notify"
print
({
id
: 0,
variant
: "center" }, "Вперёд!"); // всем, посреди экрана
console
.
log
("в консоль сервера");

print принимает игрока, id игрока или 0 — всем. Третий аргумент — где показать текст: "chat", "center" (посреди экрана), "console" (консоль игрока) или "notify" (тоже консоль; на экране игра показывает его только с developer 1).

console.log пишет строку в консоль сервера, после [amxts]; console.warn и console.error помечают свои строки как предупреждение и ошибку. Каждый принимает строку или Error — его он пишет со стеком (ошибки).

Цвета

Цвет в тексте — метка: ! и буква.

print
(
player
, "!g[Магазин]!y Ты купил !rброню");
МеткаЦветЧатМеню
!yжёлтый, обычный цвет чатадада
!rкрасныйдада
!dсерыйдада
!gзелёныйданет
!bсинийданет
!tцвет команды читающегоданет
!wбелыйнетда
!Rостаток строки — к правому краюнетда

Буква означает один и тот же цвет в чате и в меню.

Где рисуются метки
Каждое место рисует свои метки, а чужие убирает из текста, и никто об этом не предупреждает: !w и !R — для меню, !g, !b и !t — для чата. У "center", "console" и "notify" цветов нет: там текст показывается как написан, вместе с метками. Регистр в метках важен: !R — не красный.
Один цвет команды на сообщение
Красный, синий, серый и !t рисуются цветом команды, а он у сообщения в чате один: побеждает первый из них в строке, остальные показываются им же. "!rКрасный !bСиний" выводит оба слова красным.

Ошибки с v0.2

Вызов, который падает, — throw, который никто не поймал, x! на null, индекс за границей, переполнение стека, — заканчивает этот вызов, а не плагин: следующее событие, команда или таймер выполняются как обычно. Консоль сервера называет ошибку и место, где она случилась.

function price(
item
: Item | null): number {
return
item
!.price;
} function onSelect(): number { return price(null); }

Плагин, который компилирует amxts dev, ведёт запись своих вызовов: консоль называет каждый вызов, который привёл к ошибке, — с файлом, строкой и столбцом в твоём TypeScript — и под первым вызовом твоего собственного кода показывает саму строку:

[amxts] myplugin.aot: TypeError: Unexpected 'null' (not assigned or failed cast)
    at price (plugins/myplugin/shop.ts:26:9)
      26 |   return item!.price;
    at onSelect (plugins/myplugin/shop.ts:30:15)
    at timerFired (node_modules/@amxts/core/as/facade.ts:3772:2)

Плагин, который компилирует amxts build, — и тот, что сервер сам компилирует из .ts в своей папке plugins, — такой записи не ведёт: она замедляла бы нагруженный плагин (производительность). Консоль называет ошибку, а для throw, x! или неудавшейся проверки — место выражения, которое упало; переполнение стека говорит только своё сообщение:

[amxts] myplugin.aot: TypeError: Unexpected 'null' (not assigned or failed cast) (plugins/myplugin/shop.ts:26:9)

У пойманной ошибки тот же стек: error.stack — этот текст, от места, где ошибку создали, а console.error(error) его пишет. В плагине, который компилирует amxts build, error.stack — первая строка ошибки.

try {
    buy(
player
, item);
} catch (
error
) {
console
.
error
(
error
); // "Error: ..." и, в сборке для разработки, вызовы под ней
}

Строка под первым вызовом показывается, когда сервер может прочитать твой .ts, как с amxts dev. Вызовы внутри собственной библиотеки AssemblyScript — методы массива, String — показывают имя без места.

Меню с v0.2

Меню для сервера: menu-core
Настоящие меню — магазин, меню админа, голосование — это то, для чего есть официальный модуль menu-core: меню в файлах INI, YAML или JSON, которые владелец сервера правит без сборки, пункты, которые показываются и доступны по условиям, %плейсхолдеры%, списки игроков и своих строк, меню, в которые добавляют пункты Pawn-плагины, и редактор, который проверяет файл меню прямо при наборе. Добавьте его командой npx amxts module add menu-core и прочитайте Меню.

Быстрое меню в коде

Menu — собственное меню AMX Mod X как объект: заголовок, пункты и show(player). Оно входит в ядро и не требует модуля — для одного-двух меню, которые плагин собирает в коде.

interface ShopData {
    
category
: string;
} const
shop
= new
Menu
<ShopData>(({
data
}) => `!yМагазин: ${
data
.
category
}`);
shop
.
addItem
({
title
: "Броня - !y$1000",
enabled
: ({
player
}) =>
player
.
armor
< 100,
onSelect
: ({
player
}) => {
player
.
armor
= 100;
}, });
shop
.
addItem
({
title
: ({
player
}) => `Лечение !d(${
player
.
health
} HP)`,
visible
: ({
player
}) =>
player
.
isAlive
,
enabled
: ({
player
}) =>
player
.
health
< 100,
onSelect
: ({
player
,
menu
,
data
}) => {
player
.
health
= 100;
menu
.
show
(
player
,
data
); // остаётся открытым
}, });
server
.
addCommand
("/shop", ({
player
}) =>
shop
.
show
(
player
, {
category
: "all" }));
  • Пункт — объект: его title, visible — показан ли он, enabled — можно ли его выбрать, и onSelect — что делает выбор. Без visible и enabled они true; скрытый пункт не занимает места, недоступный рисуется серым и ничего не делает.
  • Каждая функция получает контекст: player — тот, кому показано меню; menu — само меню; data — то, что получил show. Data, аргумент типа, говорит, что это, — меню, показанное без данных, его не передаёт.
  • Заголовок, visible и enabled могут быть функцией или просто значением: они вычисляются при каждом show, для этого игрока.
  • После выбора меню закрывается. onSelect, который показывает его снова, оставляет его открытым.
  • Страницы, «Назад», «Дальше» и «Выход» — как у AMX Mod X: семь пунктов на странице, остальные на следующих.
  • Цветовые теги — те же, что в чате и меню: !y жёлтый, !r красный, !d серый, !w белый, !R — к правому краю.

Создайте меню один раз, на верхнем уровне файла, и показывайте сколько угодно раз. Настройки — второй аргумент:

const 
vote
= new
Menu
("!yСледующая карта?", {
perPage
: 0, // все пункты на одной странице, без «Назад» и «Дальше» (до 10)
exit
: false, // без пункта «Выход»
numberColor
: "!y", // номера пунктов жёлтые, а не красные
backText
: "Назад",
nextText
: "Дальше",
exitText
: "Закрыть",
});

Меню AMX Mod X

Для полного контроля нативы меню AMX Mod X доступны целиком: menu_create, menu_additem, menu_setprop, menu_display и остальные, с их константами (MPROP_*, MEXIT_*) — см. нативы напрямую. Обработчик передаётся именем public-функции, которое для функции делает publicFor из @amxts/core/kit. Меню старого вида — текст и клавиши, на которые оно отвечает, — это showMenu из того же набора.

Переводы

Текст на языке читающего берётся из словаря: файла в data/lang, где у каждого языка своя секция, а у каждого ключа — своя строка.

[en]
MYPLUGIN_WELCOME = Welcome, ^4%s^1!
MYPLUGIN_ON = \d[\yOn\d]

[ru]
MYPLUGIN_WELCOME = Добро пожаловать, ^4%s^1!
MYPLUGIN_ON = \d[\yВкл\d]
lang
.
load
("myplugin"); // data/lang/myplugin.txt
server
.
addEventListener
("putInServer", (
event
) => {
print
(
event
.
player
,
lang
.
translate
(
event
.
player
, "MYPLUGIN_WELCOME", [
event
.
player
.
name
]));
});

lang.translate(player, key, args) отдаёт строку ключа на языке игрока — его setinfo lang, — а null вместо игрока даёт язык сервера (amx_language). Языка, которого нет в словаре, заменяет язык сервера. %s, %d и %f (%.1f, %02d) заполняются из args, а это строки: число передаётся текстом, `${seconds}`. Место, на которое аргумента не хватило, остаётся как написано, а ключ, которого нет ни в одном словаре, возвращается как есть.

511 байт на строку
AMX Mod X читает строку словаря до 511 байт — это около 255 букв кириллицы; остаток более длинной строки обрезается.

player.language — этот самый язык, для того, чего нет в словаре, — звука, картинки: player.language == "ru". Это его setinfo lang или язык сервера, если своего у него нет или amx_client_languages равен 0.

Словарь пишет цвета так, как AMX Mod X, — \y в меню, ^4 в чате, — а lang.translate возвращает их метками: \y — это !y, \r — !r, \d — !d, \w — !w, \R — !R, ^1 — !y, ^3 — !t, ^4 — !g. Поэтому одна строка работает и в меню, и в чате; каждое место убирает метки, которых не рисует (!w в чате). ^n — перенос строки.

HUD-сообщения

player
.
showHud
("Раунд 3", {
color
: [255, 40, 40],
y
: 0.3,
hold
: 2 });
server
.
showHud
("Для всех");

Текст на экране вне чата. Настройки, крупные буквы и HudLine — на странице Игроки.

Звуки и прекэш

server
.
precache
("myplugin/hit.wav");
server
.
precache
("models/myplugin/box.mdl");
function hit(
player
: Player) {
player
.
emitSound
("myplugin/hit.wav"); // слышат все рядом
player
.
emitSound
("myplugin/hit.wav", {
volume
: 0.5,
pitch
: 120,
channel
: "voice" });
player
.
playSound
("vox/one.wav"); // слышит только он, как рацию
}

Модель, спрайт или звук, которым пользуется игра, прекэшируется, пока грузится карта: server.precache(path) на верхнем уровне файла прекэширует его тогда, а в событии "precache" — сразу. К тому же он заставляет игроков скачать файл. Звук пишется так, как его играет игра, — внутри sound/ ("myplugin/hit.wav"); модель или спрайт — со своей папкой ("models/myplugin/box.mdl"). Возвращает файл как Resource — его эффект принимает вместо спрайта или модели.

entity.emitSound(sample, options) проигрывает звук от сущности — и от игрока тоже: его слышат все рядом, и он затихает с расстоянием. У каждой настройки есть значение по умолчанию:

НастройкаПо умолчаниюЗначение
channel"auto"канал сущности: "auto", "weapon", "voice", "item", "body", "stream", "static"; новый звук на канале обрывает тот, что играет на нём, "auto" не обрывает никогда
volume1от 0 до 1
attenuation0.8как быстро затихает с расстоянием: 0 слышен по всей карте, 2 — только вблизи
pitch100в процентах: 50 — октавой ниже, до 255

player.playSound(sample) проигрывает звук одному игроку — он слышит его одинаково, где бы ни стоял, как рацию.

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

Сервер

server
.
map
; // "de_dust2"
server
.
maxPlayers
; // 32
server
.
configsDir
; // "addons/amxmodx/configs"
server
.
dataDir
; // "addons/amxmodx/data"
server
.
command
("changelevel de_nuke"); // выполнится в консоли сервера

server.command выполняет строку в консоли сервера, как будто её набрали там. Выполнить её в консоли игрока — player.command("say hi").