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 willst | Nutze |
|---|---|
| Etwas ausführen, das der Server erlauben muss (kaufen, mieten, öffnen), und seine Ablehnung anzeigen | callAction |
| Den Server nach Daten fragen | triggerServerCallback |
| Etwas hören, das der Server sendet | onServerEvent |
| Eine Frage beantworten, die der Server diesem Client stellt | onServerCallback |
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)
triggerServerCallback(name: string, args?: unknown[], timeoutMs?: number): Promise<unknown>local result, err = aiLib.triggerServerCallback(name, args, timeout) -- innerhalb eines Threads aufrufenParameter
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
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"));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)
callAction(name: string, args: unknown[] | undefined, options: CallActionOptions): Promise<({ ok: true } & Record<string, unknown>) | null>local reply = aiLib.callAction(name, args, options) -- table oder nil, innerhalb eines Threads aufrufenParameter
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
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");
}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
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
replyError(reply: unknown, fallback: string): string | nullKein 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
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(...) bekommt die rohen ArgumenteParameter
Prop
Type
Rückgabewert
void.
Beispiel
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
fromServer(): booleanKein 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)
onServerCallback(name: string, handler: (...args: unknown[]) => unknown): voidaiLib.onServerCallback(name, handler) -- der Rückgabewert des Handlers ist die AntwortParameter
Prop
Type
Rückgabewert
void.
Beispiel
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.
Lib: Client
@aicore-fivem/lib/client und die Client-Seite von aiLib: was sie sind, wie du sie in TypeScript und Lua lädst, und jede Themenseite.
Der lokale Spieler
Der gecachte Ped, Fahrzeug, Sitz und Waffe, das player-Objekt (Position, Teleport, Animationen), Bildschirm-Überblendungen, die Peds anderer Spieler und der nächstgelegene Spieler.