Pawn

Нативы

Натив — функция, которую один плагин или модуль AMX Mod X даёт остальным. Здесь нативы работают в обе стороны: плагин на TypeScript экспортирует свои для плагинов на Pawn и вызывает нативы AMX Mod X, ReAPI и других модулей там, где у @amxts/core нет своего способа.

Свои нативы: export function

Каждая export function файла плагина — натив под тем же именем, который может вызвать плагин на Pawn:

plugin
({
name
: "Points",
version
: "1.0.0",
author
: "you" });
const
saved
= new
Storage
("myplugin_points");
/** Очки игрока; 0, если очков нет. */ export function points_get(
player
: Player) {
const
text
=
saved
.
get
(
player
.
steamId
);
return
text
== null ? 0 :
parseInt
(
text
);
} /** Добавляет очки и возвращает новую сумму. */ export function points_add(
player
: Player,
amount
: number) {
const
total
= points_get(
player
) +
amount
;
saved
.
set
(
player
.
steamId
,
total
.
toString
());
return
total
;
} /** Звание игрока текстом. */ export function points_rank_name(
player
: Player) {
return points_get(
player
) >= 100 ? "Veteran" : "Rookie";
} /** Сколько сейчас стоит убийство. */ export function points_multiplier(): Float { return 1.5; }

Сборка пишет include, который нужен плагину на Pawn, — points.inc для points.ts — в dist/ проекта, с комментарием каждой функции над её строкой. amxts build --deploy и amxts dev копируют его в addons/amxmodx/scripting/include сервера:

/** Очки игрока; 0, если очков нет. */
native points_get(id);
/** Добавляет очки и возвращает новую сумму. */
native points_add(id, amount);
/** Звание игрока текстом. */
native points_rank_name(id, out[], len);
/** Сколько сейчас стоит убийство. */
native Float:points_multiplier();
#include <points>

public client_putinserver(id)
{
    new rank[32];
    points_rank_name(id, rank, charsmax(rank));
    client_print(0, print_chat, "%d очков, %s", points_get(id), rank);
}

Нативы доступны плагинам на Pawn с самого начала карты. Функция при этом остаётся обычной функцией: плагин вызывает её напрямую.

Как передаются типы

TypeScriptPawn
text: stringconst text[], читается целиком, какой бы длины ни был
count: numbercount, целое число
speed: FloatFloat:speed
flag: booleanbool:flag
values: number[]const values[], values_size
values: Float[]const Float:values[], values_size
origin: Vectorconst Float:origin[3]
player: Playerid, индекс игрока; см. ниже
player?: Playerid = 0: 0 — сервер или все — приходит как null
x = 5x = 5: значение по умолчанию попадает в include
text?: stringconst text[] = "": если не передали, функция получает ""
возвращает stringout[], len в конце; натив возвращает записанную длину
возвращает string | nullbool: и out[], len: true с текстом или false
возвращает number[] или Float[]out[], size в конце; натив возвращает, сколько записал
возвращает number, boolean, Floatрезультат натива: число, bool:, Float:
ничего не возвращаетнатив возвращает 0

Float для TypeScript — это number. Он помечает, какие числа Pawn видит как Float:, — у параметра (speed: Float) или у результата (points_multiplier(): Float). Это единственное место, где натив пишет тип результата: целое и дробное — одно и то же number.

Player для Pawn — индекс игрока, в include он называется id, а функция получает Player. Индекс, который не слот игрока, — 0, 33, -1, — до функции не доходит: натив отвечает значением по умолчанию для своего результата — 0, false, 0.0 или "", как ответил бы натив на Pawn с if (id < 1 || id > MaxClients) return 0. Пустой слот до функции доходит: проверить player.isConnected — её дело. С player?: Player 0 приходит как null — для нативов, где 0 значит сервер или всех.

Текст в обе стороны — UTF-8. Результат-строка пишется в буфер вызывающего до переданного им len (charsmax(out)) и никогда не обрезается посреди буквы.

Правила

  • Нативы — только собственные export function файла плагина: не export const и не реэкспорт. init и имена, начинающиеся с __, пропускаются.
  • Параметр или результат другого типа — string[], объект, ...args — останавливает сборку, и она называет функцию и параметр.
  • Натив принимает до 64 аргументов в том виде, в каком их передаёт Pawn: массив — это два, сам массив и его размер, как и строковый результат — буфер и его len. Натив с большим числом останавливает сборку; передайте вместо них массив.
  • После amxts_reload натив, который добавила перезагрузка, есть только у плагинов на Pawn, загруженных позже, — со следующей карты.

В тесте server.native(name, ...args) вызывает натив так, как его вызывает Pawn (Тесты).

Свой вариант готового include: include

Плагин на TypeScript может заменить плагин на Pawn, которым уже пользуются другие плагины. Он называет include того плагина, и include решает, как каждый натив выглядит для Pawn:

// shop.inc - include, с которым собраны плагины на Pawn
native Float:shop_get_price(const item[]);
native shop_get_item_name(index, name[], len);
native bool:shop_buy(id, const item[]);
native shop_print(id, const fmt[], any:...);
native shop_get_stock(const item[], &count, &Float:price);
plugin
({
name
: "Shop",
version
: "1.0.0",
author
: "you",
include
: "shop.inc" });
interface Stock {
count
: number;
price
: number;
} const
items
= ["armor", "hegrenade"];
const
prices
= [650, 300];
export function shop_get_price(
item
: string) {
const
at
=
items
.
indexOf
(
item
);
return
at
< 0 ? 0 :
prices
[
at
];
} export function shop_get_item_name(
index
: number) {
return
index
>= 0 &&
index
<
items
.
length
?
items
[
index
] : "";
} export function shop_buy(
player
: Player,
item
: string) {
return
player
.
isAlive
&&
items
.
includes
(
item
);
} export function shop_print(
player
: Player,
text
: string) { // текст приходит уже отформатированным
print
(
player
, `!g[Магазин]!y ${
text
}`);
} export function shop_get_stock(
item
: string) { // заполняет &count и &price
const
at
=
items
.
indexOf
(
item
);
if (
at
< 0) return null;
const
stock
: Stock = {
count
: 5,
price
:
prices
[
at
] };
return
stock
;
}

Функции остаются с обычными типами TypeScript — без Float, — а include говорит, как передаётся каждый аргумент:

В includeФункция
Float:x, bool:x, Tag:xx: number, читается как дробное, да/нет или целое
id, игрокplayer: Player или player?: Player, когда 0 значит всех
TeamName:teamteam: Team: "UNASSIGNED", "TERRORIST", "CT", "SPECTATOR"
const x[] = ""x?: string
const x[]x: string или x: number[] с размером после него
x[], len, не constвыход: функция возвращает строку
несколько выходов, &xфункция возвращает объект, поля которого заполняют их по порядку, или null — тогда натив возвращает 0
const fmt[], any:...text: string, уже отформатированный, как это сделал бы format, вместе с %L
any:... после других параметроваргументы после фиксированных
результат Array:string[], number[], string[][], number[][] или объект из них
результат, который никто не читаетфункция ничего не возвращает

Include ищется рядом с плагином, в папке плагинов, в папке includes/ проекта и среди include сервера (include сервера). Каждый объявленный в нём натив должен быть экспортирован: сборка назовёт недостающий. Плагин может экспортировать и больше нативов, чем есть в include; они добавляются после собственного текста include в тот include, который пишет сборка, — под именем include, так что плагины на Pawn оставляют свой #include <shop>.

Нативы напрямую

Когда в @amxts/core нет того, что нужно, вызовите натив. Каждый натив AMX Mod X, ReAPI, других модулей, описанных в include, и других плагинов — функция в @amxts/core/natives, под своим именем и с типами TypeScript:

import { 
get_member
,
get_user_info
,
rg_send_bartime2
,
user_slap
} from "@amxts/core/natives";
import {
m_rgAmmo
} from "@amxts/core/constants";
server
.
addCommand
("/plant", ({
player
}) => plant(
player
));
function plant(
player
: Player) {
rg_send_bartime2
(
player
.
id
, 10, 50.0); // Float: - это число
const
model
=
get_user_info
(
player
.
id
, "model"); // текст, который заполняет натив, - результат
const
buckshot
=
get_member
(
player
.
id
,
m_rgAmmo
, 5); // член-массив: элемент после него
user_slap
(
player
.
id
, 5);
console
.
log
(`${
player
.
name
}: ${
model
}, дробь ${
buckshot
}`);
}
В includeЗдесь
Float:xnumber
bool:xboolean
const text[]string
out[], len, заполняет нативвозвращённая string
Float:v[3]number[]
x = 5необязательный параметр с этим значением
const fmt[], any:...готовый текст: шаблонная строка
any:...ещё аргументы, каждый своего типа

Там, где Pawn принимает id, натив принимает индекс игрока, player.id. Константы из include (m_rgAmmo, EV_FL_health, …) лежат в @amxts/core/constants.

Нативы полей ReAPI — get_entvar и set_entvar, get_member и set_member, get_member_game, get_pmove, … — читают и пишут поле тем, что в нём лежит: дробное поле — это number, а не его биты. Векторному или текстовому полю указывают его тип, а члену-массиву — элемент после поля:

set_entvar(box.id, var_gravity, 0.5);
const 
gravity
= get_entvar(box.id, var_gravity); // 0.5
const
origin
= get_entvar<Vector>(box.id, var_origin); // Vector
const
held
= get_member<string>(
player
.
id
, m_szAnimExtention); // "knife"
const
ammo
= get_member(
player
.
id
, m_rgAmmo, 3); // элемент 3
set_member(
player
.
id
, m_rgAmmo, 90, 3);

Натив, чей include заканчивается на any:..., — engfunc, dllfunc, pev, ExecuteHam, … — принимает ещё до двенадцати аргументов, каждый тем, что он есть: число, логическое значение, текст, вектор ([x, y, z] или Vector), Player. То, что натив возвращает через аргумент, — вектор, который он заполняет, текст в ret[], len, число в &value, — попадает в переданный массив или в Ref:

import { 
dllfunc
,
engfunc
} from "@amxts/core/natives";
import {
DLLFunc_ClientConnect
,
EngFunc_CreateFakeClient
,
EngFunc_PrecacheModel
,
EngFunc_TraceLine
,
EngFunc_VecToAngles
,
IGNORE_MONSTERS
} from "@amxts/core/constants";
const
model
=
engfunc
(
EngFunc_PrecacheModel
, "models/myplugin/box.mdl");
const
id
=
engfunc
(
EngFunc_CreateFakeClient
, "Dummy");
engfunc
(
EngFunc_TraceLine
, [0, 0, 64],
player
.
origin
,
IGNORE_MONSTERS
,
player
.
id
);
const
angles
= [0, 0, 0];
engfunc
(
EngFunc_VecToAngles
, [1, 1, 0],
angles
); // теперь angles — [0, 45, 0]
const
reason
= new
Ref
(""); // текст, который пишет натив
if (!
dllfunc
(
DLLFunc_ClientConnect
,
id
, "Dummy", "127.0.0.1",
reason
))
console
.
log
(
reason
.
value
);

Число в таком хвосте уходит как Float: там, где include говорит, что натив читает его так: engfunc и dllfunc — по функции движка (скорости EngFunc_RunPlayerMove), ExecuteHam — по функции Ham, pev, set_pev и global_get — по полю, get_tr2, get_es, get_uc, get_cd и их пары set_ — по члену, forward_return — с FMV_FLOAT. В остальных — целое число.

Если это есть в @amxts/core, берите его — player.health, а не get_user_health(player.id), — а натив — если нет. Редактор знает каждый натив: наведите на него, чтобы увидеть комментарий из include.

Текст в натив
Текст обрезают AMX Mod X и игра, а не путь до них. Натив получает строку целиком, до 16383 байт — столько AMX Mod X читает, и пишет в буфер не больше 16384 ячеек, какой бы длины он ни был. Натив с const fmt[], any:... (client_print, server_print, format, …) форматирует строку не длиннее 4095 байт, а server_print печатает из них 254. Игра показывает около 190 байт строки чата — примерно 95 букв кириллицы — и 126 байт строки консоли, отправленной через client_print.

::: warning Обратный вызов по имени Натив, который берёт обратный вызов по имени паблика, — register_think, set_native_filter — принимает имя, которое даёт publicFor(handler, key): key — любое имя, уникальное в плагине. Такую регистрацию не отменить, и она переживает перезагрузку, поэтому пустое имя значит, что она уже сделана:

import { 
publicFor
} from "@amxts/core/kit";
import {
register_think
} from "@amxts/core/natives";
const
pub
=
publicFor
(onThink, "think:myplugin_box");
if (
pub
.
length
> 0)
register_think
("myplugin_box",
pub
);

:::