AI Core
API-ReferenzLib: Client

Mit dem Server sprechen

Den Server fragen und auf seine Antwort warten, Server-Aktionen mit übersetzten Ablehnungen ausführen, auf Server-Events hören und Server-Callbacks beantworten.

Ein Client-Skript vertraut nie sich selbst: Es fragt den Server, und der Server entscheidet. Diese Helfer transportieren diese Fragen und Antworten. Die Server-Hälften stehen unter Client-Eingaben (onClientCallback, onClientEvent, triggerClientCallback).

Du willstNutze
Etwas ausführen, das der Server erlauben muss (kaufen, mieten, öffnen), und seine Ablehnung anzeigencallAction
Den Server nach Daten fragentriggerServerCallback
Etwas hören, das der Server sendetonServerEvent
Eine Frage beantworten, die der Server diesem Client stelltonServerCallback

triggerServerCallback

Fragt den Server und wartet auf seine Antwort. Der Server antwortet mit onClientCallback(name, ...). Nutze es, um Daten zu lesen, die der Server hält (eine Preisliste, die eigenen Einträge eines Spielers). Für eine Aktion, die abgelehnt werden kann, nutze callAction: Es zeigt die Ablehnung für dich an.

Seite: Client · Wartet: ja (Promise in TypeScript, hält in Lua den Thread an)

Signatur
triggerServerCallback(name: string, args?: unknown[], timeoutMs?: number): Promise<unknown>
Lua
local result, err = aiLib.triggerServerCallback(name, args, timeout) -- innerhalb eines Threads aufrufen

Parameter

Prop

Type

Rückgabewert

TypeScript: Promise<unknown>. Es wird mit dem erfüllt, was der Server-Handler zurückgegeben hat (null, wenn er nichts zurückgab). Der Wert wird nicht geprüft: Teste seinen Typ, bevor du ihn nutzt. Das Promise wird abgelehnt, wenn der Server-Handler geworfen hat, das Server-Gate den Aufruf abgelehnt hat (Ratenlimit, ein Guard, falsche Argumente, eine Sperre) oder innerhalb von timeoutMs keine Antwort kam.

Lua: zwei Werte. result ist die Antwort; err ist bei Erfolg nil, 'failed', wenn der Server abgelehnt hat oder fehlgeschlagen ist, 'timeout', wenn keine Antwort rechtzeitig kam. Bei einem Fehler ist result nil.

Beispiel

resource/client/index.ts
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,
);
resource/server/index.ts
import { onClientCallback, resourceExport } from "@aicore-fivem/lib/server";

const core = resourceExport("ai-core");

onClientCallback("myscript:cash", (source) => core.GetMoney(source, "cash"));

Behandle in TypeScript immer die Ablehnung (.catch(() => null) oder try): Ein abgelehnter oder abgelaufener Aufruf wird abgelehnt. Der Server begrenzt jeden Callback standardmäßig auf 20 Aufrufe pro Sekunde und Spieler, rufe ihn also nie in einer Schleife oder in jedem Frame auf. Das Übertragungsformat ist in beiden Sprachen dasselbe: Ein Lua-Client kann einen TypeScript-Server aufrufen und umgekehrt.

callAction

Führt eine Aktion auf dem Server aus und zeigt die Ablehnung für dich an. Der Server-Handler (onClientCallback) antwortet mit einem ActionResult: { ok: true, ...data }, wenn es geklappt hat, oder fail("LANG_KEY"), wenn er abgelehnt hat. Bei einer Ablehnung, einem Timeout oder einer Ablehnung durch das Gate schlägt callAction den Fehlerschlüssel in der Sprachdatei deiner Ressource nach und zeigt ihn als Fehlerbenachrichtigung an. Nutze es für jeden Button, der etwas ändert: kaufen, verkaufen, mieten, reparieren.

Seite: Client · Wartet: ja (Promise in TypeScript, hält in Lua den Thread an)

Signatur
callAction(name: string, args: unknown[] | undefined, options: CallActionOptions): Promise<({ ok: true } & Record<string, unknown>) | null>
Lua
local reply = aiLib.callAction(name, args, options) -- table oder nil, innerhalb eines Threads aufrufen

Parameter

Prop

Type

CallActionOptions

Prop

Type

Rückgabewert

Promise<({ ok: true } & Record<string, unknown>) | null>: die ganze ok-Antwort (mit allen zusätzlichen Feldern, die der Server hinzugefügt hat), wenn die Aktion geklappt hat; null, wenn sie abgelehnt wurde, fehlgeschlagen oder abgelaufen ist (die Benachrichtigung wird bereits angezeigt, außer bei silent). Die zusätzlichen Felder werden nicht geprüft: Teste den Typ jedes Felds, das du liest. Lua gibt die Antworttabelle oder nil zurück.

Beispiel

resource/client/index.ts
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; // die Ablehnung ist bereits auf dem Bildschirm
  const paid = typeof reply.paid === "number" ? reply.paid : 0;
  notify(Locale("BOUGHT", paid), "success");
}
resource/server/index.ts
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 };
  },
);

Der Fehlerschlüssel wird in der Sprachdatei der Ressource nachgeschlagen, die callAction aufruft, also lege jeden Schlüssel, mit dem der Server antworten kann (NO_MONEY, NO_ROOM, UNKNOWN_ITEM), und den fallback-Schlüssel in die locales/en.json dieser Ressource. Ein dort fehlender Schlüssel wird als der Schlüssel selbst angezeigt.

isActionOk

Sagt, ob eine Antwort ein ok-ActionResult ist (ok ist genau true). Nutze es, wenn du triggerServerCallback selbst aufrufst und die Antwort von Hand liest.

Seite: Client und Server (aus /shared, von /client erneut exportiert) · Wartet: nein

Signatur
isActionOk(reply: unknown): reply is { ok: true } & Record<string, unknown>

Kein Lua-Zwilling: Teste type(reply) == 'table' and reply.ok == true.

Parameter

Prop

Type

Rückgabewert

boolean: true, wenn reply ein Objekt (kein Array) ist, dessen ok true ist. In TypeScript verengt es reply außerdem auf { ok: true } & Record<string, unknown>.

Beispiel

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

Der Sprachschlüssel einer abgelehnten Antwort: ihr error-Feld, oder fallback, wenn die Antwort fehlt oder keinen Fehlertext hat. Nutze es mit isActionOk, wenn du den Fehler auf deine eigene Art anzeigst (in einer NUI-Seite statt einer Benachrichtigung).

Seite: Client und Server (aus /shared, von /client erneut exportiert) · Wartet: nein

Signatur
replyError(reply: unknown, fallback: string): string | null

Kein Lua-Zwilling.

Parameter

Prop

Type

Rückgabewert

string | null: null, wenn die Antwort ok ist; das error der Antwort, wenn es ein nicht leerer String ist; sonst fallback.

Beispiel

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

Hört auf ein Net-Event, das der Server mit emitNet sendet (TriggerClientEvent in Lua). Aufrufe, die nicht vom Server kamen (ein lokales emit einer anderen Ressource), werden verworfen, sodass ein Cheat-Menü sie nicht fälschen kann. Jeder ai-*-Client hört auf diese Weise auf den Server, nie mit einem nackten onNet. In TypeScript muss ein Handler, der Argumente nimmt, einen args-Parser angeben: Er prüft die rohen Werte, und null verwirft den Aufruf.

Seite: Client · Wartet: nein

Signatur
onServerEvent(name: string, handler: () => unknown): void
onServerEvent<A extends readonly unknown[]>(name: string, options: { args: (raw: unknown[]) => A | null }, handler: (...args: A) => unknown): void
Lua
aiLib.onServerEvent(name, handler) -- handler(...) bekommt die rohen Argumente

Parameter

Prop

Type

Rückgabewert

void.

Beispiel

resource/client/index.ts
import { onServerEvent, Points } from "@aicore-fivem/lib/client";

let renting = false;
const points = new Points();

// Keine Argumente: die Kurzform.
onServerEvent("myscript:reset", () => points.clear());

// Ein boolesches Argument: parsen, null verwirft einen fehlerhaften Aufruf.
onServerEvent(
  "myscript:state",
  { args: ([active]) => (typeof active === "boolean" ? [active] : null) },
  (active) => {
    renting = active;
    points.refresh();
  },
);

Der Server sendet das Event mit emitNet("myscript:state", source, true) (TypeScript) oder TriggerClientEvent('myscript:state', source, true) (Lua). Lua-Handler bekommen die rohen Argumente: Prüfe ihre Typen selbst.

fromServer

Ob das gerade behandelte Net-Event vom Server kam. onServerEvent nutzt es bereits; du brauchst es nur in einem nackten onNet-Handler, den du nicht ersetzen kannst.

Seite: Client · Wartet: nein

Signatur
fromServer(): boolean

Kein Lua-Zwilling: In einem Lua-Net-Event-Handler ist source bei lokalen Emits leer.

Rückgabewert

boolean: true, wenn das aktuelle Event die numerische Source des Servers trägt; false bei einem lokalen Emit einer beliebigen Ressource.

Beispiel

import { fromServer } from "@aicore-fivem/lib/client";

onNet("legacy:event", (value: unknown) => {
  if (!fromServer()) return; // ein lokales Emit, nicht der Server
  console.log("server said", value);
});

Lies es vor dem ersten await des Handlers: Nach einem await ist das aktuelle Event weg und die Antwort falsch.

onServerCallback

Beantwortet eine Frage, die der Server diesem Client mit triggerClientCallback(name, source, ...) stellt. Der Wert, den dein Handler zurückgibt (oder mit dem sein Promise erfüllt wird), wird zurückgeschickt. Ein geworfener Fehler antwortet mit einem Fehlschlag, und das Promise des Servers wird abgelehnt.

Seite: Client · Wartet: nein (der Handler darf async sein)

Signatur
onServerCallback(name: string, handler: (...args: unknown[]) => unknown): void
Lua
aiLib.onServerCallback(name, handler) -- der Rückgabewert des Handlers ist die Antwort

Parameter

Prop

Type

Rückgabewert

void.

Beispiel

resource/client/index.ts
import { onServerCallback, player } from "@aicore-fivem/lib/client";

// Der Server fragt, wie weit dieser Spieler von einem Punkt entfernt steht.
onServerCallback("myscript:distance", (x, y, z) => {
  if (typeof x !== "number" || typeof y !== "number" || typeof z !== "number") return null;
  return player.distanceTo({ x, y, z });
});

In Lua antwortet ein Handler, der false zurückgibt, mit nil (der Server bekommt null): Gib true / nil, eine Zahl oder eine Tabelle zurück, wenn der Server false von "keine Antwort" unterscheiden muss. Die Antwort kommt vom Computer des Spielers, daher kann ein Cheater sie ändern. Nutze Client-Antworten für Dinge, die nur der Client weiß (was auf dem Bildschirm ist, eine lokale Entity), und nie als Beweis für Geld oder Items.

Auf dieser Seite