Hablar con el servidor
Preguntar al servidor y esperar su respuesta, ejecutar acciones del servidor con rechazos traducidos, escuchar eventos del servidor y responder a callbacks del servidor.
Un script de cliente nunca confía en sí mismo: pregunta al servidor, y el servidor decide. Estas utilidades llevan esas preguntas y respuestas. Las mitades de servidor están en Entrada del cliente (onClientCallback, onClientEvent, triggerClientCallback).
| Quieres | Usa |
|---|---|
| Ejecutar algo que el servidor debe permitir (comprar, alquilar, abrir) y mostrar su rechazo | callAction |
| Pedir datos al servidor | triggerServerCallback |
| Escuchar algo que envía el servidor | onServerEvent |
| Responder a una pregunta que el servidor hace a este cliente | onServerCallback |
triggerServerCallback
Pregunta al servidor y espera su respuesta. El servidor responde con onClientCallback(name, ...). Úsalo para leer datos que guarda el servidor (una lista de precios, los registros propios de un jugador). Para una acción que puede rechazarse, usa callAction: muestra el rechazo por ti.
Lado: Cliente · Espera: sí (Promise en TypeScript, cede el hilo en Lua)
triggerServerCallback(name: string, args?: unknown[], timeoutMs?: number): Promise<unknown>local result, err = aiLib.triggerServerCallback(name, args, timeout) -- llamar dentro de un hiloParámetros
Prop
Type
Devuelve
TypeScript: Promise<unknown>. Se resuelve con lo que devolvió el handler del servidor (null cuando no devolvió nada). El valor no se comprueba: prueba su tipo antes de usarlo. La promise se rechaza cuando el handler del servidor lanzó un error, la puerta del servidor rechazó la llamada (límite de frecuencia, una guarda, argumentos incorrectos, un bloqueo) o no llegó ninguna respuesta dentro de timeoutMs.
Lua: dos valores. result es la respuesta; err es nil si todo fue bien, 'failed' cuando el servidor rechazó o falló, 'timeout' cuando no llegó ninguna respuesta a tiempo. Con un error, result es nil.
Ejemplo
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"));En TypeScript gestiona siempre el rechazo (.catch(() => null) o try): una llamada rechazada o agotada se rechaza. El servidor limita cada callback a 20 llamadas por segundo por jugador por defecto, así que nunca lo llames en un bucle ni en cada frame. El formato en la red es el mismo en ambos lenguajes: un cliente Lua puede llamar a un servidor TypeScript y al revés.
callAction
Ejecuta una acción en el servidor y muestra el rechazo por ti. El handler del servidor (onClientCallback) responde un ActionResult: { ok: true, ...data } cuando funcionó, o fail("LANG_KEY") cuando lo rechazó. Con un rechazo, un tiempo agotado o un rechazo de la puerta, callAction busca la clave de error en el archivo de idioma de tu recurso y la muestra como notificación de error. Úsalo para cada botón que cambia algo: comprar, vender, alquilar, reparar.
Lado: Cliente · Espera: sí (Promise en TypeScript, cede el hilo en Lua)
callAction(name: string, args: unknown[] | undefined, options: CallActionOptions): Promise<({ ok: true } & Record<string, unknown>) | null>local reply = aiLib.callAction(name, args, options) -- table o nil, llamar dentro de un hiloParámetros
Prop
Type
CallActionOptions
Prop
Type
Devuelve
Promise<({ ok: true } & Record<string, unknown>) | null>: la respuesta ok completa (con los campos extra que añadió el servidor) cuando la acción funcionó; null cuando se rechazó, falló o se agotó el tiempo (la notificación ya se mostró, salvo con silent). Los campos extra no se comprueban: prueba el tipo de cada uno que leas. Lua devuelve la tabla de respuesta o nil.
Ejemplo
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; // el rechazo ya está en pantalla
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 };
},
);La clave de error se busca en el archivo de idioma del recurso que llama a callAction, así que pon cada clave que pueda responder el servidor (NO_MONEY, NO_ROOM, UNKNOWN_ITEM) y la clave fallback en el locales/en.json de ese recurso. Una clave que falta ahí se muestra como la propia clave.
isActionOk
Indica si una respuesta es un ActionResult ok (ok es exactamente true). Úsalo cuando llamas tú mismo a triggerServerCallback y lees la respuesta a mano.
Lado: Cliente y servidor (desde /shared, reexportado por /client) · Espera: no
isActionOk(reply: unknown): reply is { ok: true } & Record<string, unknown>Sin equivalente en Lua: prueba type(reply) == 'table' and reply.ok == true.
Parámetros
Prop
Type
Devuelve
boolean: true cuando reply es un objeto (no un array) cuyo ok es true. En TypeScript también estrecha reply a { ok: true } & Record<string, unknown>.
Ejemplo
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
La clave de idioma de una respuesta rechazada: su campo error, o fallback cuando falta la respuesta o no tiene texto de error. Úsalo con isActionOk cuando muestras el error a tu manera (en una página NUI en lugar de una notificación).
Lado: Cliente y servidor (desde /shared, reexportado por /client) · Espera: no
replyError(reply: unknown, fallback: string): string | nullSin equivalente en Lua.
Parámetros
Prop
Type
Devuelve
string | null: null cuando la respuesta es ok; el error de la respuesta cuando es un string no vacío; en otro caso fallback.
Ejemplo
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
Escucha un evento de red que el servidor envía con emitNet (TriggerClientEvent en Lua). Las llamadas que no vinieron del servidor (el emit local de otro recurso) se descartan, así que un menú de trampas no puede falsificarlas. Cada cliente ai-* escucha al servidor de esta forma, nunca con un onNet a secas. En TypeScript, un handler que recibe argumentos debe indicar un parser args: comprueba los valores en bruto, y null descarta la llamada.
Lado: Cliente · Espera: no
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(...) recibe los argumentos en brutoParámetros
Prop
Type
Devuelve
void.
Ejemplo
import { onServerEvent, Points } from "@aicore-fivem/lib/client";
let renting = false;
const points = new Points();
// Sin argumentos: la forma corta.
onServerEvent("myscript:reset", () => points.clear());
// Un argumento booleano: procésalo, null descarta una llamada incorrecta.
onServerEvent(
"myscript:state",
{ args: ([active]) => (typeof active === "boolean" ? [active] : null) },
(active) => {
renting = active;
points.refresh();
},
);El servidor envía el evento con emitNet("myscript:state", source, true) (TypeScript) o TriggerClientEvent('myscript:state', source, true) (Lua). Los handlers Lua reciben los argumentos en bruto: comprueba sus tipos tú mismo.
fromServer
Si el evento de red que se está gestionando ahora mismo vino del servidor. onServerEvent ya lo usa; solo lo necesitas en un handler onNet a secas que no puedas reemplazar.
Lado: Cliente · Espera: no
fromServer(): booleanSin equivalente en Lua: en un handler de evento de red Lua, source está vacío para los emits locales.
Devuelve
boolean: true cuando el evento actual lleva el source numérico del servidor; false para un emit local de cualquier recurso.
Ejemplo
import { fromServer } from "@aicore-fivem/lib/client";
onNet("legacy:event", (value: unknown) => {
if (!fromServer()) return; // un emit local, no el servidor
console.log("server said", value);
});Léelo antes del primer await del handler: después de un await el evento actual ya no existe y la respuesta es incorrecta.
onServerCallback
Responde a una pregunta que el servidor hace a este cliente con triggerClientCallback(name, source, ...). El valor que devuelve tu handler (o con el que se resuelve su promise) se envía de vuelta. Un error lanzado responde un fallo, y la promise del servidor se rechaza.
Lado: Cliente · Espera: no (el handler puede ser async)
onServerCallback(name: string, handler: (...args: unknown[]) => unknown): voidaiLib.onServerCallback(name, handler) -- el valor de retorno del handler es la respuestaParámetros
Prop
Type
Devuelve
void.
Ejemplo
import { onServerCallback, player } from "@aicore-fivem/lib/client";
// El servidor pregunta a qué distancia está este jugador de un punto.
onServerCallback("myscript:distance", (x, y, z) => {
if (typeof x !== "number" || typeof y !== "number" || typeof z !== "number") return null;
return player.distanceTo({ x, y, z });
});En Lua, un handler que devuelve false responde nil (el servidor recibe null): devuelve true / nil, un número o una tabla cuando el servidor deba distinguir false de "sin respuesta". La respuesta viene del ordenador del jugador, así que un tramposo puede cambiarla. Usa las respuestas del cliente para cosas que solo sabe el cliente (lo que hay en pantalla, una entidad local), y nunca como prueba para dinero u objetos.
Lib: cliente
@aicore-fivem/lib/client y el lado de cliente de aiLib: qué son, cómo cargarlos en TypeScript y Lua, y cada página temática.
El jugador local
El ped, el vehículo, el asiento y el arma en caché, el objeto player (posición, teletransporte, animaciones), fundidos de pantalla, los peds de otros jugadores y el jugador más cercano.