AI Core
API başvurusuLib: server

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:

  1. Onu gerçek, bağlı bir oyuncu gönderdi (aynı adı tetikleyen başka bir resource değil).
  2. Hız sınırı (sen rate vermezsen oyuncu başına saniyede 20 callback, 10 event).
  3. Korumaların (hasCharacter(), near(...), isAdmin() ya da kendi yazdıkların).
  4. Ham değerleri kontrol edilmiş değerlere çeviren ya da çağrıyı reddeden args ayrıştırıcın.
  5. İ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).

TypeScript
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)

Signature
onClientCallback(name: string, handler: (source: number) => unknown, options?: ClientInputOptions): void
onClientCallback<A>(name: string, options: ParsedInputOptions<A>, handler: (source: number, ...args: A) => unknown): void
Lua
aiLib.onClientCallback(name, handler, options) -- handler(source, ...): any

Parametreler

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 triggerServerCallback promise’i hemen reddedilir (callAction kendi fallback metnini gösterir); Lua’da aiLib.triggerServerCallback nil, '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

Signature
onClientEvent(name: string, handler: (source: number) => unknown, options?: ClientInputOptions): void
onClientEvent<A>(name: string, options: ParsedInputOptions<A>, handler: (source: number, ...args: A) => unknown): void
Lua
aiLib.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
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

Signature
isAdmin(ace?: string): Guard
Lua
local 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

Signature
hasCharacter(): Guard

Lua 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

Signature
near(point: Vec3 | ((source: number) => Vec3 | null), metres: number): Guard

Lua karşılığı yok: #(GetEntityCoords(GetPlayerPed(source)) - point) değerini kendin karşılaştır.

Parametreler

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

Signature
eventSource(): number

Lua 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)

Signature
triggerClientCallback(name: string, source: number, args?: unknown[], timeoutMs?: number): Promise<unknown>
Lua
local result, err = aiLib.triggerClientCallback(name, source, args, timeout) -- bir thread içinde çağır

Parametreler

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)

Signature
progressFor(source: number, options: ProgressForOptions): Promise<boolean>
Lua
local finished = aiLib.progressFor(source, options) -- boolean, bir thread içinde çağır

Parametreler

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)

Signature
minigameFor(source: number, options: MinigameForOptions): Promise<MinigameOutcome>
Lua
local outcome = aiLib.minigameFor(source, options) -- 'won' | 'lost' | 'broken', bir thread içinde çağır

Parametreler

Prop

Type

MinigameForOptions

Prop

Type

Dönüş değeri

Promise<MinigameOutcome>, burada MinigameOutcome = "won" | "lost" | "broken":

  • "won": oyuncu kazandı (ve minMs’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.

Signature
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.

Signature
start(source: number, data: T, durationMs: number): number

Prop

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.

Signature
take(source: number, token: number): T | null

Dö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

Signature
peek(source: number): T | null

Dönüş değeri: çalışan aksiyonun verisi (aksiyonu bitirmeden) ya da null.

PendingActions.cancel

Signature
cancel(source: number): void

Oyuncunun aksiyonunu sonuçsuz bitirir.

PendingActions.size

Signature
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.

Signature
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): void

Prop

Type

  • check, kontrol edilmiş argümanları (args yoksa boş bir dizi) ya da çağrının çalışmaması gerekiyorsa null döndürür (gerçek oyuncu değil, hız, koruma, ayrıştırıcı).
  • run, lock açıksa task’ı oyuncu başına kilit altında çalıştırır; o oyuncunun başka bir çağrısı hâlâ çalışıyorsa bunun yerine busy dö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.

Bu sayfada