Client girdisi
Client’lara güvenli cevap ver: onClientCallback, onClientEvent, korumalar (guard), triggerClientCallback, server’ın doğruladığı ilerleme çubukları ve minigame’ler, PendingActions.
Bir client’ın gönderdiği her şey bir hileci tarafından taklit edilebilir. Her ai-* server’ı client’ları yalnızca kapı üzerinden dinler: onClientCallback ve onClientEvent. Handler’ın çalışmadan önce kapı şunları bu sırayla kontrol eder:
- Onu gerçek, bağlı bir oyuncu gönderdi (aynı adı tetikleyen başka bir resource değil).
- Hız sınırı (sen
ratevermezsen oyuncu başına saniyede 20 callback, 10 event). - Korumaların (
hasCharacter(),near(...),isAdmin()ya da kendi yazdıkların). - Ham değerleri kontrol edilmiş değerlere çeviren ya da çağrıyı reddeden
argsayrıştırıcın. - İsteğe bağlı bir kilit: oyuncu başına aynı anda tek çağrı.
Sebebi olan bir reddetme, oyuncu adı ve girdi adıyla server konsoluna yazılır; oyuncu ve girdi başına 10 saniyede en fazla bir satır (aradaki satırlar bir sonrakine sayı olarak eklenir).
import { onClientCallback, onClientEvent, hasCharacter, near, isAdmin } from "@aicore-fivem/lib/server";
import { fail, oneArg, readId, readInt, readText } from "@aicore-fivem/lib/shared";Girdi okuyucular (readInt, readId, readText, oneArg, asArgs) ve fail, Lib: shared ve fivem sayfasında belgelenir. Bir aksiyon ActionResult ile cevap verir: { ok: true, ... } ya da fail("LANG_KEY").
Kapı
onClientCallback
Bir client’ın triggerServerCallback(name, ...) (ya da callAction) çağrısına cevap verir ve handler’ın dönüş değerini geri gönderir. Client’ın cevap beklediği her istek için kullan: satın alma, kullanma, bir ekran açma. İki biçimi vardır: argümansız (name, handler, options?) ve argümanlı (name, { args, ... }, handler); ikincisinde ayarlar önce gelir, böylece handler’ın yalnızca kontrol edilmiş değerleri görür.
Taraf: Server · Bekler: hayır (handler async olabilir; cevap gönderilmeden önce promise’i beklenir)
onClientCallback(name: string, handler: (source: number) => unknown, options?: ClientInputOptions): void
onClientCallback<A>(name: string, options: ParsedInputOptions<A>, handler: (source: number, ...args: A) => unknown): voidaiLib.onClientCallback(name, handler, options) -- handler(source, ...): anyParametreler
Prop
Type
ClientInputOptions
Prop
Type
ParsedInputOptions
ClientInputOptions’ın tüm alanları, artı:
Prop
Type
Dönüş değeri
void. Client’ın aldığı cevap:
- handler çalıştıysa handler’ın dönüş değeri;
- kapı çağrıyı reddettiyse, kilit doluysa ya da handler hata fırlattıysa bir başarısızlık. TypeScript’te client’ın
triggerServerCallbackpromise’i hemen reddedilir (callActionkendifallbackmetnini gösterir); Lua’daaiLib.triggerServerCallbacknil, 'failed'döndürür.
Örnek
import { onClientCallback, hasCharacter, near, inventory } from "@aicore-fivem/lib/server";
import { fail, oneArg, readInt } from "@aicore-fivem/lib/shared";
const CRATE = { x: 120.5, y: -1050.2, z: 29.2 };
// Client: callAction("my-crates:take", [2], { fallback: "TAKE_FAILED" })
onClientCallback(
"my-crates:take",
{
args: oneArg((v) => readInt(v, 1, 5)),
guards: [hasCharacter(), near(CRATE, 3)],
lock: true,
},
(source, count) => {
if (!inventory.canCarryItem(source, "water", count)) return fail("CANNOT_CARRY");
inventory.addItem(source, "water", count);
return { ok: true };
},
);Lua sürümünde guards ve args yoktur: options = { rate?, lock?, ace? } alır (ace: oyuncunun bu ACE yetkisine sahip olması gerekir, ai-core admin’leri için ai.admin) ve handler ham değerleri alır. Her argümanı kullanmadan önce kendin kontrol et (tip, aralık, sahiplik, mesafe). TypeScript’te client değerlerini asla args dışında okuma.
onClientEvent
Bir client’ın gönderdiği net event’i aynı kapı üzerinden dinler. Client’ın cevap beklemediği tek yönlü mesajlar için kullan (bir ekranı kapatmak, bildirilen bir hücre). Client girdisi için asla çıplak bir onNet kullanma. onClientCallback ile aynı iki biçime sahiptir.
Taraf: Server · Bekler: hayır
onClientEvent(name: string, handler: (source: number) => unknown, options?: ClientInputOptions): void
onClientEvent<A>(name: string, options: ParsedInputOptions<A>, handler: (source: number, ...args: A) => unknown): voidaiLib.onClientEvent(name, handler, options) -- handler(source, ...), options { rate?, ace? }Parametreler
Prop
Type
Dönüş değeri
void.
Örnek
import { onClientEvent, PlayerMap } from "@aicore-fivem/lib/server";
const openShops = new PlayerMap<string>();
// Client: emitNet("my-shop:close")
onClientEvent("my-shop:close", (source) => {
openShops.delete(source);
});Lua sürümü options = { rate?, ace? } alır (lock, guards ve args yok). Başka bir resource’tan gelen yerel bir TriggerEvent hiçbir oyuncu taşımaz ve iki tarafta da yok sayılır.
Korumalar
Koruma (guard), oyuncunun server id’sini alıp true (geç), false (sessizce reddet) ya da bir string (reddet ve string’i sebep olarak logla) döndüren bir fonksiyondur.
type Guard = (source: number) => boolean | string;Bir kontrol birkaç girdide tekrarlanıyorsa kendi korumanı yaz:
import type { Guard } from "@aicore-fivem/lib/server";
import { online } from "@aicore-fivem/lib/server";
const onDutyPolice: Guard = (source) => online.onDutyIn(source, ["police"]) || "not on duty";isAdmin
Oyuncunun bir ACE yetkisi varsa geçen bir koruma: ai-core’un admin yetkisi ai.admin ya da senin verdiğin yetki.
Taraf: Server · Bekler: hayır
isAdmin(ace?: string): Guardlocal allowed = aiLib.isAdmin(source, ace) -- boolean, doğrudan bir kontrol (koruma değil)Parametreler
Prop
Type
Dönüş değeri
Guard: not allowed (<ace>) sebebiyle reddeder. Lua karşılığı doğrudan true ya da false döndürür; source 0 ya da altı için false.
Örnek
import { onClientCallback, isAdmin, medical } from "@aicore-fivem/lib/server";
onClientCallback("my-admin:reviveSelf", (source) => ({ ok: medical.revive(source) }), {
guards: [isAdmin()],
});hasCharacter
Oyuncunun dünyada bir karakteri varsa (karakter ekranında değilse) geçen bir koruma.
Taraf: Server · Bekler: hayır
hasCharacter(): GuardLua karşılığı yok: aiLib.online.character(source) ~= nil kontrol et.
Dönüş değeri
Guard: no character loaded sebebiyle reddeder.
Örnek
import { onClientCallback, hasCharacter, online } from "@aicore-fivem/lib/server";
onClientCallback("my-id:name", (source) => ({ ok: true, name: online.name(source) }), {
guards: [hasCharacter()],
});near
Oyuncunun ped’i bir noktaya metres kadar yakınsa geçen bir koruma; mesafe server’da ölçülür (client bu konuda yalan söyleyemez).
Taraf: Server · Bekler: hayır
near(point: Vec3 | ((source: number) => Vec3 | null), metres: number): GuardLua karşılığı yok: #(GetEntityCoords(GetPlayerPed(source)) - point) değerini kendin karşılaştır.
Parametreler
Prop
Type
Dönüş değeri
Guard: too far (<metres> m) sebebiyle reddeder.
Örnek
import { onClientCallback, near, PlayerMap } from "@aicore-fivem/lib/server";
import type { Vec3 } from "@aicore-fivem/lib/shared";
const openAt = new PlayerMap<Vec3>();
onClientCallback("my-shop:list", (source) => ({ ok: true, items: [] }), {
guards: [near((source) => openAt.get(source) ?? null, 4)],
});eventSource
İşlenen net event’i gönderen oyuncunun server id’si. onClientCallback ve onClientEvent içinde source’u ilk argüman olarak alırsın; bu yüzden buna yalnızca ham bir FiveM onNet ya da on handler’ında ihtiyacın olur.
Taraf: Server · Bekler: hayır
eventSource(): numberLua karşılığı yok: Lua global source’u okur.
Dönüş değeri
number: oyuncunun server id’si (FiveM’in global source’u).
Örnek
import { eventSource } from "@aicore-fivem/lib/server";
on("my-script:serverOnly", () => {
const source = eventSource(); // önce bunu oku
console.log(`event from ${source}`);
});Onu ilk await’ten önce oku. FiveM, handler yield ettikten sonra global’i sıfırlar; daha sonraki bir okuma yanlış oyuncuyu verir.
Client’a sormak
triggerClientCallback
Bir oyuncunun client’ına bir soru sorar ve cevabı bekler. Client onServerCallback(name, handler) ile cevap verir (Lib: client). Yalnızca sorduğun oyuncu cevap verebilir.
Taraf: Server · Bekler: evet (TypeScript’te Promise, Lua’da thread’i yield eder)
triggerClientCallback(name: string, source: number, args?: unknown[], timeoutMs?: number): Promise<unknown>local result, err = aiLib.triggerClientCallback(name, source, args, timeout) -- bir thread içinde çağırParametreler
Prop
Type
Dönüş değeri
Promise<unknown>: client’ın cevabıyla çözülür. Cevap zamanında gelmediyse ya da client handler’ı başarısız olduysa bir Error ile reddedilir. Cevap bir client’tan gelir: kullanmadan önce ayrıştır.
Lua cevabı ya da nil ve 'timeout' / 'failed' döndürür.
Örnek
import { addCommand, triggerClientCallback, notify } from "@aicore-fivem/lib/server";
import { readText } from "@aicore-fivem/lib/shared";
// Client: onServerCallback("my-admin:street", () => locationName().street)
addCommand(
"street",
{ help: "Shows the street a player stands on", restricted: true, params: [{ name: "id", type: "playerId" }] },
async (source, { id }) => {
const answer = await triggerClientCallback("my-admin:street", id, [], 5000).catch(() => null);
const street = readText(answer, 100);
if (source > 0) notify(source, street ?? "No answer", street ? "info" : "error");
},
);Server’ın güvendiği zamanlı aksiyonlar
progressFor
ai-lib’in ilerleme çubuğunu oyuncunun ekranında çalıştırır ve bitene kadar bekler. Server’ın güvenmesi gereken her zamanlı aksiyon için kullan (eşya kullanmak, tamir etmek, soymak): yalnızca çubuk client’ta bittiğinde ve server’da yeterli zaman geçtiğinde true cevabını verir; böylece bir hileci onu atlayamaz. Hiç client kodu yazmazsın: ai-lib her resource için cevap verir.
Taraf: Server · Bekler: evet (TypeScript’te Promise, Lua’da thread’i yield eder)
progressFor(source: number, options: ProgressForOptions): Promise<boolean>local finished = aiLib.progressFor(source, options) -- boolean, bir thread içinde çağırParametreler
Prop
Type
ProgressForOptions
Prop
Type
Dönüş değeri
Promise<boolean>: çubuk bittiyse true. Oyuncu iptal ettiyse, öldüyse, araca bindiyse (allowVehicle olmadan), oyundan çıktıysa, karakter değiştirdiyse, çok hızlı cevap verdiyse ya da duration artı 15 saniye içinde cevap gelmediyse false.
Örnek
import { onClientCallback, progressFor, inventory, vehicleNearPlayer, createLocale } from "@aicore-fivem/lib/server";
import { fail, oneArg, readId } from "@aicore-fivem/lib/shared";
const Locale = createLocale();
onClientCallback(
"my-mechanic:repair",
{ args: oneArg(readId), lock: true },
async (source, netId) => {
if (!vehicleNearPlayer(source, netId, { metres: 4 })) return fail("TOO_FAR");
if (!inventory.hasItem(source, "repairkit")) return fail("NO_KIT");
const done = await progressFor(source, {
label: Locale("REPAIRING"),
duration: 8000,
anim: { dict: "mini@repair", clip: "fixing_a_player" },
canCancel: true,
});
if (!done) return fail("CANCELLED");
// Çubuk çalışırken dünya değişmiş olabilir: tekrar kontrol et.
const vehicle = vehicleNearPlayer(source, netId, { metres: 4 });
if (!vehicle) return fail("TOO_FAR");
if (!inventory.removeItem(source, "repairkit")) return fail("NO_KIT");
// Araç sağlığı, entity’nin sahibi olan client’ta ayarlanır.
emitNet("my-mechanic:applyRepair", NetworkGetEntityOwner(vehicle), netId);
return { ok: true };
},
);Aynı oyuncu için ikinci bir progressFor ilkini false ile bitirir. Await’ten sonra mesafeyi ve stoğu tekrar kontrol et: çubuk çalışırken dünya değişmiş olabilir. Lua’da oyundan çıkma ya da karakter değiştirme erken algılanmaz: çağrı, zaman aşımı (duration + 15 sn) dolduğunda false cevabını verir.
minigameFor
ai-lib’in minigame’lerinden birini (skill check ya da maymuncuk) bir server akışı için oyuncunun ekranında oynatır ve sonucu bekler. Kapı maymuncuklamak, kasa kırmak, düz kontak yapmak için kullan. Sonuç client’ın sözüdür; bu yüzden kazanmanın ne getireceğine yine server karar verir (bir şans zarı, bir progressFor, kendi kontrolleri).
Taraf: Server · Bekler: evet (TypeScript’te Promise, Lua’da thread’i yield eder)
minigameFor(source: number, options: MinigameForOptions): Promise<MinigameOutcome>local outcome = aiLib.minigameFor(source, options) -- 'won' | 'lost' | 'broken', bir thread içinde çağırParametreler
Prop
Type
MinigameForOptions
Prop
Type
Dönüş değeri
Promise<MinigameOutcome>, burada MinigameOutcome = "won" | "lost" | "broken":
"won": oyuncu kazandı (veminMs’ten hızlı değil)."lost": oyuncu ıskaladı, vazgeçti, çok hızlı kazandı ya da 120 saniye içinde cevap vermedi."broken": yalnızca maymuncuk, son maymuncuk kırıldı. Maymuncuk eşyasını al.
Örnek
import { onClientCallback, minigameFor, inventory, hasCharacter, near, isNear } from "@aicore-fivem/lib/server";
import { fail } from "@aicore-fivem/lib/shared";
const SAFE = { x: 28.2, y: -1339.2, z: 29.5 };
onClientCallback(
"my-heist:pickSafe",
async (source) => {
if (!inventory.hasItem(source, "lockpick")) return fail("NO_LOCKPICK");
const outcome = await minigameFor(source, { game: "lockpick", difficulty: "hard", pins: 3, picks: 2, minMs: 1500 });
if (outcome === "broken") inventory.removeItem(source, "lockpick");
if (outcome !== "won") return fail("LOCKPICK_FAILED");
if (!isNear(source, SAFE, 2)) return fail("TOO_FAR");
inventory.addItem(source, "money_bag");
return { ok: true };
},
{ guards: [hasCharacter(), near(SAFE, 2)], lock: true },
);Bir server akışı için asla kendi client minigame callback’ini yazma: minigameFor kullan. minMs olmadan, değiştirilmiş bir client hemen "won" cevabı verebilir. Yukarıdaki 1500 bir örnektir: kendi ayarlarının en hızlı dürüst oynanışını ölç.
PendingActions
Client’ın bir pencere açıp sonucu daha sonra gönderdiği (not yazmak, bir seçenek seçmek), zamanlı çubuğu olmayan akışlar için oyuncu başına bekleyen tek bir aksiyon tutar. start, client’ın geri gönderdiği bir token verir; take aksiyonu yalnızca o token eşleşirse bitirir. Oyuncu oyundan çıktığında ya da karakteri bıraktığında girdiler serbest bırakılır (bir karakter değişimi eski karakterin aksiyonunu asla yenisine ödemez). Modül seviyesinde bir tane oluştur.
Taraf: Server · Bekler: hayır
Lua karşılığı yok: oyuncu başına token’ların olduğu bir Lua tablosu tut.
new PendingActions<T>(options?: { minRatio?: number })Constructor parametreleri
Prop
Type
PendingActions.start
Oyuncunun aksiyonunu başlatır (hâlâ çalışan bir aksiyon varsa onun yerine geçer) ve token’ını döndürür.
start(source: number, data: T, durationMs: number): numberProp
Type
Dönüş değeri number: token, 1 ile 2147483647 arasında bir tam sayı. Onu client’a gönder; geri readId ile oku.
PendingActions.take
token çalışan aksiyonun token’ıysa ve yeterli zaman geçtiyse aksiyonu bitirir ve verisini döndürür.
take(source: number, token: number): T | nullDönüş değeri T | null: veri ya da hiçbir aksiyon çalışmıyorsa, token eşleşmiyorsa veya cevap çok erken geldiyse null. Yanlış bir token çalışan aksiyona dokunmaz (eski ya da sahte bir token onu asla iptal etmez); eşleşen bir token ise çok erken olsa bile onu bitirir.
PendingActions.peek
peek(source: number): T | nullDönüş değeri: çalışan aksiyonun verisi (aksiyonu bitirmeden) ya da null.
PendingActions.cancel
cancel(source: number): voidOyuncunun aksiyonunu sonuçsuz bitirir.
PendingActions.size
readonly size: numberŞu an kaç aksiyon çalışıyor.
Örnek
import { onClientCallback, PendingActions, inventory, hasCharacter } from "@aicore-fivem/lib/server";
import { fail, readId, readText } from "@aicore-fivem/lib/shared";
const notes = new PendingActions<{ item: string }>({ minRatio: 0 });
// Adım 1: client yazmak ister; server bir token ile cevap verir.
onClientCallback(
"my-notes:start",
(source) => {
if (!inventory.hasItem(source, "paper")) return fail("NO_PAPER");
const token = notes.start(source, { item: "paper" }, 60_000);
return { ok: true, token };
},
{ guards: [hasCharacter()] },
);
// Adım 2: client token’ı ve metni gönderir.
onClientCallback(
"my-notes:finish",
{
args: ([token, text]) => {
const id = readId(token);
const body = readText(text, 200);
return id !== null && body !== null ? [id, body] : null;
},
lock: true,
},
(source, token, text) => {
const action = notes.take(source, token);
if (!action) return fail("NOTE_EXPIRED");
if (!inventory.removeItem(source, action.item)) return fail("NO_PAPER");
inventory.addItem(source, "note", 1, { text });
return { ok: true };
},
);İleri seviye
ClientGate
onClientCallback ve onClientEvent’in arkasındaki sınıf. Ona yalnızca ham bir event üzerine kendi girdi türünü kurarken ihtiyacın olur; script’ler yukarıdaki iki fonksiyonu kullanır.
Taraf: Server · Bekler: hayır
Lua karşılığı yok.
new ClientGate<A>(name: string, options: ClientInputOptions & { args?: (raw: unknown[]) => A | null }, defaultRate: number)
check(source: number, raw: unknown[]): A | null
run<T, B>(source: number, busy: B, task: () => Promise<T> | T): Promise<T | B>
release(source: number): voidProp
Type
check, kontrol edilmiş argümanları (argsyoksa boş bir dizi) ya da çağrının çalışmaması gerekiyorsanulldöndürür (gerçek oyuncu değil, hız, koruma, ayrıştırıcı).run,lockaçıksatask’ı oyuncu başına kilit altında çalıştırır; o oyuncunun başka bir çağrısı hâlâ çalışıyorsa bunun yerinebusydöndürür.release, oyuncunun hız ve kilit girdilerini serbest bırakır. Her kapıplayerDropped’ta kendiliğinden serbest bırakılır.