Связь с сервером
Спросить сервер и дождаться ответа, выполнить серверные действия с переведёнными отказами, слушать события сервера и отвечать на серверные колбэки.
Клиентский скрипт никогда не доверяет сам себе: он спрашивает сервер, а сервер решает. Эти помощники переносят такие вопросы и ответы. Серверные половины описаны на странице Ввод от клиента (onClientCallback, onClientEvent, triggerClientCallback).
| Вы хотите | Используйте |
|---|---|
| Выполнить то, что должен разрешить сервер (купить, арендовать, открыть), и показать его отказ | callAction |
| Запросить данные у сервера | triggerServerCallback |
| Услышать то, что отправляет сервер | onServerEvent |
| Ответить на вопрос, который сервер задаёт этому клиенту | onServerCallback |
triggerServerCallback
Спрашивает сервер и ждёт ответа. Сервер отвечает через onClientCallback(name, ...). Используйте, чтобы читать данные, которые хранит сервер (прайс-лист, собственные записи игрока). Для действия, которое может быть отклонено, используйте callAction: он сам покажет отказ.
Сторона: Клиент · Ждёт: да (Promise в TypeScript, в Lua приостанавливает поток)
triggerServerCallback(name: string, args?: unknown[], timeoutMs?: number): Promise<unknown>local result, err = aiLib.triggerServerCallback(name, args, timeout) -- вызывайте внутри потокаПараметры
Prop
Type
Возвращает
TypeScript: Promise<unknown>. Выполняется с тем, что вернул серверный обработчик (null, если он ничего не вернул). Значение не проверяется: проверьте его тип перед использованием. Promise отклоняется, если серверный обработчик выбросил ошибку, шлюз сервера отклонил вызов (ограничение частоты, guard-условие, неверные аргументы, блокировка) или ответ не пришёл в течение timeoutMs.
Lua: два значения. result это ответ; err равен nil при успехе, 'failed', если сервер отказал или произошла ошибка, 'timeout', если ответ не пришёл вовремя. При ошибке result равен nil.
Пример
import { createLocale, notify, triggerServerCallback } from "@aicore-fivem/lib/client";
const Locale = createLocale<typeof import("../../locales/en.json")>();
RegisterCommand(
"mycash",
async () => {
const cash = await triggerServerCallback("myscript:cash").catch(() => null);
if (typeof cash !== "number") return notify(Locale("ERROR"), "error");
notify(Locale("YOUR_CASH", cash), "info");
},
false,
);import { onClientCallback, resourceExport } from "@aicore-fivem/lib/server";
const core = resourceExport("ai-core");
onClientCallback("myscript:cash", (source) => core.GetMoney(source, "cash"));В TypeScript всегда обрабатывайте отклонение (.catch(() => null) или try): отклонённый вызов или вызов с истёкшим тайм-аутом отклоняется. По умолчанию сервер ограничивает каждый колбэк 20 вызовами в секунду на игрока, поэтому никогда не вызывайте его в цикле или каждый кадр. Формат передачи одинаков в обоих языках: клиент на Lua может вызвать сервер на TypeScript и наоборот.
callAction
Выполняет действие на сервере и сам показывает отказ. Серверный обработчик (onClientCallback) отвечает ActionResult: { ok: true, ...data }, если всё получилось, или fail("LANG_KEY"), если отказал. При отказе, тайм-ауте или отказе шлюза callAction ищет ключ ошибки в языковом файле вашего ресурса и показывает его как уведомление об ошибке. Используйте для каждой кнопки, которая что-то меняет: купить, продать, арендовать, отремонтировать.
Сторона: Клиент · Ждёт: да (Promise в TypeScript, в Lua приостанавливает поток)
callAction(name: string, args: unknown[] | undefined, options: CallActionOptions): Promise<({ ok: true } & Record<string, unknown>) | null>local reply = aiLib.callAction(name, args, options) -- таблица или nil, вызывайте внутри потокаПараметры
Prop
Type
CallActionOptions
Prop
Type
Возвращает
Promise<({ ok: true } & Record<string, unknown>) | null>: весь успешный ответ (с любыми дополнительными полями, которые добавил сервер), если действие получилось; null, если оно было отклонено, завершилось ошибкой или тайм-аутом (уведомление уже показано, если не задан silent). Дополнительные поля не проверяются: проверяйте тип каждого, которое читаете. Lua возвращает таблицу ответа или nil.
Пример
import { callAction, createLocale, notify } from "@aicore-fivem/lib/client";
const Locale = createLocale<typeof import("../../locales/en.json")>();
export async function buy(itemId: string): Promise<void> {
const reply = await callAction("myshop:buy", [itemId], { fallback: "BUY_FAILED" });
if (!reply) return; // отказ уже на экране
const paid = typeof reply.paid === "number" ? reply.paid : 0;
notify(Locale("BOUGHT", paid), "success");
}import { inventory, onClientCallback, resourceExport } from "@aicore-fivem/lib/server";
import { fail, oneArg, readText } from "@aicore-fivem/lib/shared";
const core = resourceExport("ai-core");
const PRICES: Record<string, number> = { water: 5, bread: 8 };
onClientCallback(
"myshop:buy",
{ args: oneArg((v) => readText(v, 32)) },
(source, item) => {
const price = PRICES[item];
if (price === undefined) return fail("UNKNOWN_ITEM");
if (!inventory.canCarryItem(source, item, 1)) return fail("NO_ROOM");
if (!core.RemoveMoney(source, "cash", price, "shop")) return fail("NO_MONEY");
inventory.addItem(source, item, 1);
return { ok: true, paid: price };
},
);Ключ ошибки ищется в языковом файле ресурса, который вызывает callAction, поэтому положите каждый ключ, которым может ответить сервер (NO_MONEY, NO_ROOM, UNKNOWN_ITEM), и ключ fallback в locales/en.json этого ресурса. Отсутствующий там ключ показывается как есть.
isActionOk
Сообщает, является ли ответ успешным ActionResult (ok ровно true). Используйте, когда сами вызываете triggerServerCallback и читаете ответ вручную.
Сторона: Клиент и сервер (из /shared, реэкспортируется из /client) · Ждёт: нет
isActionOk(reply: unknown): reply is { ok: true } & Record<string, unknown>Lua-аналога нет: проверьте type(reply) == 'table' and reply.ok == true.
Параметры
Prop
Type
Возвращает
boolean: true, если reply является объектом (не массивом), у которого ok равно true. В TypeScript также сужает тип reply до { ok: true } & Record<string, unknown>.
Пример
import { isActionOk, replyError, triggerServerCallback } from "@aicore-fivem/lib/client";
export async function buyWater(): Promise<void> {
const reply = await triggerServerCallback("myshop:buy", ["water"]).catch(() => null);
if (isActionOk(reply)) console.log("bought");
else console.log("refused with", replyError(reply, "BUY_FAILED"));
}replyError
Языковой ключ отклонённого ответа: его поле error или fallback, если ответа нет или в нём нет текста ошибки. Используйте вместе с isActionOk, когда показываете ошибку по-своему (на странице NUI вместо уведомления).
Сторона: Клиент и сервер (из /shared, реэкспортируется из /client) · Ждёт: нет
replyError(reply: unknown, fallback: string): string | nullLua-аналога нет.
Параметры
Prop
Type
Возвращает
string | null: null, если ответ успешный; error ответа, если это непустая строка; иначе fallback.
Пример
import { replyError, triggerServerCallback } from "@aicore-fivem/lib/client";
export async function buyFromPage(item: string): Promise<void> {
const reply = await triggerServerCallback("myshop:buy", [item]).catch(() => null);
const error = replyError(reply, "BUY_FAILED");
if (error) SendNUIMessage({ action: "error", data: error });
}onServerEvent
Слушает сетевое событие, которое сервер отправляет через emitNet (TriggerClientEvent в Lua). Вызовы, пришедшие не от сервера (локальный emit другого ресурса), отбрасываются, поэтому чит-меню не может их подделать. Каждый клиент ai-* слушает сервер именно так, никогда через голый onNet. В TypeScript обработчик, который принимает аргументы, должен указать парсер args: он проверяет сырые значения, а null отбрасывает вызов.
Сторона: Клиент · Ждёт: нет
onServerEvent(name: string, handler: () => unknown): void
onServerEvent<A extends readonly unknown[]>(name: string, options: { args: (raw: unknown[]) => A | null }, handler: (...args: A) => unknown): voidaiLib.onServerEvent(name, handler) -- handler(...) получает сырые аргументыПараметры
Prop
Type
Возвращает
void.
Пример
import { onServerEvent, Points } from "@aicore-fivem/lib/client";
let renting = false;
const points = new Points();
// Без аргументов: короткая форма.
onServerEvent("myscript:reset", () => points.clear());
// Один булев аргумент: разбираем его, null отбрасывает неверный вызов.
onServerEvent(
"myscript:state",
{ args: ([active]) => (typeof active === "boolean" ? [active] : null) },
(active) => {
renting = active;
points.refresh();
},
);Сервер отправляет событие через emitNet("myscript:state", source, true) (TypeScript) или TriggerClientEvent('myscript:state', source, true) (Lua). Обработчики Lua получают сырые аргументы: проверяйте их типы сами.
fromServer
Пришло ли от сервера сетевое событие, которое обрабатывается прямо сейчас. onServerEvent уже использует это; вам это нужно только в голом обработчике onNet, который нельзя заменить.
Сторона: Клиент · Ждёт: нет
fromServer(): booleanLua-аналога нет: в обработчике сетевого события Lua source пуст для локальных вызовов.
Возвращает
boolean: true, если текущее событие несёт числовой source сервера; false для локального вызова любым ресурсом.
Пример
import { fromServer } from "@aicore-fivem/lib/client";
onNet("legacy:event", (value: unknown) => {
if (!fromServer()) return; // локальный вызов, не сервер
console.log("server said", value);
});Читайте это до первого await обработчика: после await текущее событие уже ушло и ответ будет неверным.
onServerCallback
Отвечает на вопрос, который сервер задаёт этому клиенту через triggerClientCallback(name, source, ...). Значение, которое возвращает ваш обработчик (или с которым выполняется его promise), отправляется обратно. Выброшенная ошибка даёт ответ-неудачу, и promise сервера отклоняется.
Сторона: Клиент · Ждёт: нет (обработчик может быть async)
onServerCallback(name: string, handler: (...args: unknown[]) => unknown): voidaiLib.onServerCallback(name, handler) -- возвращаемое значение обработчика является ответомПараметры
Prop
Type
Возвращает
void.
Пример
import { onServerCallback, player } from "@aicore-fivem/lib/client";
// Сервер спрашивает, как далеко этот игрок стоит от точки.
onServerCallback("myscript:distance", (x, y, z) => {
if (typeof x !== "number" || typeof y !== "number" || typeof z !== "number") return null;
return player.distanceTo({ x, y, z });
});В Lua обработчик, который возвращает false, отвечает nil (сервер получает null): возвращайте true / nil, число или таблицу, если серверу нужно отличать false от «нет ответа». Ответ приходит с компьютера игрока, поэтому читер может его изменить. Используйте ответы клиента для того, что знает только клиент (что на экране, локальная сущность), и никогда как доказательство для денег или предметов.
Lib: клиент
@aicore-fivem/lib/client и клиентская сторона aiLib: что это такое, как загружать их в TypeScript и Lua, и все тематические страницы.
Локальный игрок
Кэшированные пед, транспорт, место и оружие, объект player (позиция, телепорт, анимации), затемнения экрана, педы других игроков и ближайший игрок.