AI Core
Dokumentacja APILib: serwer

Gracze i postacie

online, onCharacter, notify, gracze w pobliżu, pozycje i magazyny na gracza: PlayerMap, Cooldowns, KeyedCooldowns, SourceLock, RateLimit, KeyWatchers.

TypeScript
import { online, onCharacter, notify, playersNear, coordsOf, PlayerMap, Cooldowns } from "@aicore-fivem/lib/server";

Gracz to połączony klient z id serwerowym (source). Postać to osoba, którą gra; gracz na ekranie wyboru postaci nie ma wczytanej postaci. ai-core jest właścicielem graczy i postaci; te funkcje je czytają.

online

online to kopia wczytanych graczy ai-core w twoim zasobie: postacie, członkostwa w pracach i gangach ze stopniami, listy członków każdej pracy i gangu oraz służba. Czyta ai-core raz przy pierwszym użyciu, a potem śledzi zdarzenia ai-core, więc odczyt to wyszukanie w pamięci. Używaj go na gorących ścieżkach (każda wiadomość na czacie, każdy alert) zamiast wywoływać eksporty ai-core.

W Lua aiLib.online pyta eksporty ai-core przy każdym wywołaniu: w porządku dla akcji, nie dla każdego ticku. Ma character, name, jobs, gangs, jobGrade, gangGrade, dutyJob i sources; reszta nie ma odpowiednika w Lua.

online.sources

Id serwerowe każdego gracza z wczytaną postacią.

Strona: Serwer · Czeka: nie

Sygnatura
online.sources(): IterableIterator<number>
Lua
local list = aiLib.online.sources() -- integer[]

Zwracana wartość

IterableIterator<number> (Lua: tablica). Gracze na ekranie wyboru postaci nie są w niej.

Przykład

import { online, notify } from "@aicore-fivem/lib/server";

for (const source of online.sources()) notify(source, "Server restart in 5 minutes", "warning", 10_000);

online.size

Ilu graczy ma wczytaną postać.

Strona: Serwer · Czeka: nie

Sygnatura
readonly online.size: number

Brak odpowiednika w Lua: #aiLib.online.sources().

online.character

Wczytana postać gracza.

Strona: Serwer · Czeka: nie

Sygnatura
online.character(source: number): CharacterInfo | null
Lua
local character = aiLib.online.character(source) -- tabela albo nil

Prop

Type

Zwracana wartość

CharacterInfo (id, citizenid, slot, firstname, lastname, birthdate, sex, nationality) albo null (Lua nil), gdy gracz nie ma wczytanej postaci.

Przykład

const character = online.character(source);
if (character) console.log(`${character.citizenid} is ${character.firstname}`);

online.charId

Id wczytanej postaci gracza (id z bazy danych, stałe między ponownymi połączeniami).

Strona: Serwer · Czeka: nie

Sygnatura
online.charId(source: number): number | undefined

Brak odpowiednika w Lua: (aiLib.online.character(source) or {}).id.

Zwraca id postaci albo undefined, gdy żadna nie jest wczytana. Dane, które muszą przetrwać ponowne połączenie (cooldowny, własność), kluczuj tym id, a nie source.

const charId = online.charId(source);
if (charId === undefined) return fail("NO_CHARACTER");

online.name

"Imię Nazwisko" postaci albo nazwa gracza w FiveM, gdy żadna postać nie jest wczytana.

Strona: Serwer · Czeka: nie

Sygnatura
online.name(source: number, options?: { fallback?: boolean }): string
Lua
local name = aiLib.online.name(source, fallback) -- fallback: boolean?

Prop

Type

Zwraca string.

notify(target, `${online.name(source)} sent you a gift`, "success");

online.jobs

Każda praca, której gracz jest członkiem, na służbie czy nie. ai-core pozwala postaci mieć kilka prac (maxJobs w panelu), z jedną pracą na służbie naraz.

Strona: Serwer · Czeka: nie

Sygnatura
online.jobs(source: number): readonly string[]
Lua
local memberships = aiLib.online.jobs(source) -- { { job = 'police', grade = 2 }, ... }

Zwraca nazwy prac (pusto, gdy nie ma żadnej albo nie ma postaci). Odpowiednik w Lua zwraca tabele członkostwa { job, grade }, nie nazwy.

const isMechanic = online.jobs(source).includes("mechanic");

online.gangs

Każdy gang, w którym jest gracz.

Strona: Serwer · Czeka: nie

Sygnatura
online.gangs(source: number): readonly string[]
Lua
local memberships = aiLib.online.gangs(source) -- { { gang = 'ballas', grade = 0 }, ... }

Zwraca nazwy gangów (pusto, gdy nie ma żadnego). Odpowiednik w Lua zwraca tabele członkostwa { gang, grade }.

if (online.gangs(source).length === 0) return fail("NOT_IN_GANG");

online.jobGrade

Stopień gracza w jednej pracy.

Strona: Serwer · Czeka: nie

Sygnatura
online.jobGrade(source: number, job: string): number | null
Lua
local grade = aiLib.online.jobGrade(source, job) -- integer albo nil

Prop

Type

Zwraca numer stopnia albo null (Lua nil), gdy gracz nie jest członkiem. Stopień 0 to członek: porównuj z null, a nie przez sprawdzenie prawdziwości.

const grade = online.jobGrade(source, "police");
if (grade === null || grade < 3) return fail("RANK_TOO_LOW");

online.gangGrade

Stopień gracza w jednym gangu.

Strona: Serwer · Czeka: nie

Sygnatura
online.gangGrade(source: number, gang: string): number | null
Lua
local grade = aiLib.online.gangGrade(source, gang) -- integer albo nil

Zwraca stopień albo null (Lua nil), gdy nie jest członkiem.

const boss = (online.gangGrade(source, "ballas") ?? -1) >= 4;

online.jobMembers

Każdy gracz z członkostwem w pracy, na służbie czy nie.

Strona: Serwer · Czeka: nie

Sygnatura
online.jobMembers(job: string): ReadonlySet<number>

Brak odpowiednika w Lua.

Zwraca żywy zbiór id serwerowych (pusty, gdy nie ma nikogo). Zmienia się, gdy gracze dołączają do pracy i ją opuszczają: skopiuj go ([...set]) przed await.

for (const source of online.jobMembers("mechanic")) notify(source, "A customer is waiting", "info");

online.gangMembers

Każdy gracz w gangu.

Strona: Serwer · Czeka: nie

Sygnatura
online.gangMembers(gang: string): ReadonlySet<number>

Brak odpowiednika w Lua. Zwraca żywy zbiór id serwerowych; skopiuj go przed await.

const members = [...online.gangMembers("ballas")];

online.dutyJob

Praca, w której gracz jest na służbie.

Strona: Serwer · Czeka: nie

Sygnatura
online.dutyJob(source: number): string | null
Lua
local job = aiLib.online.dutyJob(source) -- string albo nil

Zwraca nazwę pracy albo null (Lua nil), gdy jest poza służbą.

if (online.dutyJob(source) !== "ambulance") return fail("NOT_ON_DUTY");

online.onDutyIn

Czy gracz jest na służbie w jednej z kilku prac (wszystkie prace policyjne, wszystkie prace EMS).

Strona: Serwer · Czeka: nie

Sygnatura
online.onDutyIn(source: number, jobs: readonly string[]): boolean

Brak odpowiednika w Lua. Zwraca true, gdy praca na służbie jest w jobs.

const POLICE = ["police", "sheriff"];
if (!online.onDutyIn(source, POLICE)) return fail("NOT_POLICE");

online.onDutyFor

Gracze na służbie w jednej pracy.

Strona: Serwer · Czeka: nie

Sygnatura
online.onDutyFor(job: string): ReadonlySet<number>

Brak odpowiednika w Lua. Zwraca żywy zbiór id serwerowych; skopiuj go przed await.

for (const medic of online.onDutyFor("ambulance")) notify(medic, "Patient at Pillbox", "info");

online.countOnDuty

Ilu graczy jest na służbie w kilku pracach.

Strona: Serwer · Czeka: nie

Sygnatura
online.countOnDuty(jobs: readonly string[]): number

Brak odpowiednika w Lua. Zwraca łączną liczbę (koszt rośnie z liczbą prac, nie graczy).

if (online.countOnDuty(["police", "sheriff"]) < 2) return fail("NOT_ENOUGH_POLICE");

online.allows

Czy gracz może korzystać z miejsca zablokowanego regułą dostępu (prace, gangi, minimalne stopnie, służba). allows klienta używa tej samej reguły, więc to, co widzi gracz, jest tym, z czego serwer pozwala mu korzystać.

Strona: Serwer · Czeka: nie

Sygnatura
online.allows(source: number, rule: AccessRule): boolean

Brak odpowiednika w Lua.

Prop

Type

Zwraca true, gdy listy są puste, gracz jest w jednym z gangs od jego minimalnego stopnia albo w jednej z jobs od jej minimalnego stopnia (i jest w niej na służbie, gdy ustawiono onDuty).

import { online } from "@aicore-fivem/lib/server";
import { readAccess, fail } from "@aicore-fivem/lib/shared";

const rule = readAccess(stashRecord); // { jobs: ["police"], gangs: [], grades: { police: 2 }, onDuty: true }
if (!online.allows(source, rule)) return fail("NO_ACCESS");

Cykl życia postaci

onCharacter

Uruchamia twój kod, gdy postać wchodzi do świata i gdy go opuszcza. Odtwarza też postacie już obecne w świecie, gdy twój zasób (ponownie) startuje, i obsługuje wyjścia graczy, więc nie potrzebujesz obok handlera playerDropped.

Strona: Serwer · Czeka: nie

Sygnatura
onCharacter(handlers: CharacterHandlers): void
Lua
aiLib.onCharacter({ loaded = function(source, character) end, unloaded = function(source, charId, reason) end })

CharacterHandlers

Prop

Type

Zwracana wartość

void.

Przykład

import { onCharacter, PlayerMap } from "@aicore-fivem/lib/server";

const playtime = new PlayerMap<{ charId: number; since: number }>();

onCharacter({
  loaded(source, character) {
    playtime.set(source, { charId: character.id, since: Date.now() });
  },
  unloaded(source, charId, reason) {
    const entry = playtime.get(source);
    if (entry) console.log(`character ${charId} played ${Date.now() - entry.since} ms (${reason})`);
    playtime.delete(source);
  },
});

Wywołaj ją raz na najwyższym poziomie pliku serwera. PlayerMap wyżej opróżnia się sam przy wyjściu gracza; handler unloaded i tak działa przy wyjściu najpierw, więc możesz tam zapisać dane.

Rozmowa z graczami

notify

Pokazuje kartę powiadomienia na ekranie jednego gracza (rysuje ją ai-lib albo UI powiadomień wybrane przez właściciela w panelu ai-core).

Strona: Serwer · Czeka: nie

Sygnatura
notify(source: number, text: string, kind?: NotificationKind, duration?: number): void
Lua
aiLib.notify(source, text, kind, duration)

Parametry

Prop

Type

Zwracana wartość

void.

Przykład

import { notify, createLocale } from "@aicore-fivem/lib/server";

const Locale = createLocale();
notify(source, Locale("PAID", 250), "success");

Żeby odpowiedzieć na akcję klienta, zwróć fail("KEY") zamiast wywoływać notify: callAction klienta sam pokazuje przetłumaczony błąd.

isOnline

Czy gracz jest nadal połączony. Sprawdź to po długim await, zanim cokolwiek dasz.

Strona: Serwer · Czeka: nie

Sygnatura
isOnline(source: number): boolean

Brak odpowiednika w Lua: GetPlayerName(source) ~= nil.

Zwraca true, dopóki gracz jest połączony.

await new Promise((resolve) => setTimeout(resolve, 60_000));
if (!isOnline(source)) return;

Gracze w pobliżu

Te funkcje pytają ai-core, którzy gracze są w swoim zasięgu OneSync (około 424 metrów), i mierzą tylko tych kilku, zamiast przechodzić po każdym graczu na serwerze. Promień większy niż około 424 m nie znajduje nikogo dalej.

playersNear

Gracze w promieniu radius metrów od gracza.

Strona: Serwer · Czeka: nie

Sygnatura
playersNear(source: number, radius: number): number[]
Lua
local list = aiLib.playersNear(source, radius) -- integer[]

Prop

Type

Zwraca tablicę id serwerowych. TypeScript zawiera samo source; Lua je pomija.

for (const other of playersNear(source, 20)) if (other !== source) notify(other, "You hear a scream", "warning");

playersNearEntity

Gracze w promieniu radius metrów od encji sieciowej (pojazdu, propa): jej właściciel i gracze w zasięgu właściciela.

Strona: Serwer · Czeka: nie

Sygnatura
playersNearEntity(entity: number, radius: number): number[]
Lua
local list = aiLib.playersNearEntity(entity, radius) -- integer[]

Prop

Type

Zwraca tablicę id serwerowych. Pusta, gdy encja nie istnieje albo żaden gracz nie jest jej właścicielem (encja należąca tylko do serwera nie ma nikogo w pobliżu).

const witnesses = playersNearEntity(vehicle, 30);

nearestPlayerTo

Najbliższy inny gracz w odległości metres od gracza, który przechodzi twój filtr.

Strona: Serwer · Czeka: nie

Sygnatura
nearestPlayerTo(source: number, metres: number, filter?: (other: number) => boolean): number | null
Lua
local other = aiLib.nearestPlayerTo(source, metres, filter) -- integer albo nil

Prop

Type

Zwraca id serwerowe najbliższego pasującego gracza albo null (Lua nil), gdy nie ma żadnego.

const target = nearestPlayerTo(source, 2, (other) => online.character(other) !== null);
if (target === null) return fail("NOBODY_NEAR");

Pozycje

Brak odpowiedników w Lua: Lua ma matematykę wektorów (GetEntityCoords(GetPlayerPed(source)), #(a - b)).

coordsOf

Pozycja peda gracza tak, jak widzi ją serwer.

Strona: Serwer · Czeka: nie

Sygnatura
coordsOf(source: number): Vec3

Zwraca Vec3 { x, y, z }.

const { x, y, z } = coordsOf(source);

entityCoords

Pozycja dowolnej encji (serwerowa kopia synchronizacji jej właściciela).

Strona: Serwer · Czeka: nie

Sygnatura
entityCoords(entity: number): Vec3

Zwraca Vec3.

import { distance } from "@aicore-fivem/lib/shared";
if (distance(entityCoords(vehicle), coordsOf(source)) > 5) return fail("TOO_FAR");

isNear

Czy ped gracza jest w odległości metres od punktu. Używa jej strażnik near.

Strona: Serwer · Czeka: nie

Sygnatura
isNear(source: number, point: Vec3, metres: number): boolean

Prop

Type

Zwraca true, gdy jest w zasięgu.

if (!isNear(source, { x: 25.7, y: -1347.3, z: 29.5 }, 3)) return fail("TOO_FAR");

vehicleOf

Pojazd, w którym siedzi gracz.

Strona: Serwer · Czeka: nie

Sygnatura
vehicleOf(source: number, options?: { driver?: boolean }): number

Prop

Type

Zwraca encję pojazdu albo 0 pieszo (albo poza miejscem kierowcy przy driver: true).

const vehicle = vehicleOf(source, { driver: true });
if (!vehicle) return fail("NOT_DRIVING");

Magazyny na gracza

Żaden z nich nie ma odpowiednika w Lua: użyj tabeli Lua i czyść ją w playerDropped. Twórz każdy magazyn raz na poziomie modułu, nigdy wewnątrz handlera.

PlayerMap

Map kluczowana id serwerowym gracza, która sama się opróżnia, gdy gracz wyjdzie. Używaj jej do wszystkiego, co trzymasz na gracza (otwarty ekran, wybrany przedmiot).

Strona: Serwer · Czeka: nie

Sygnatura
new PlayerMap<V>(options?: { onDrop?: (source: number, value: V) => void })

Prop

Type

Ma każdą metodę Map z JavaScript (get, set, has, delete, size, iteracja).

import { PlayerMap } from "@aicore-fivem/lib/server";

const carrying = new PlayerMap<string>({
  onDrop: (source, item) => console.log(`${source} left while carrying ${item}`),
});
carrying.set(source, "box");

Cooldowns

Cooldowny na gracza według zegara gry, zwalniane, gdy gracz wyjdzie.

Strona: Serwer · Czeka: nie

Sygnatura
new Cooldowns()
ready(source: number): boolean
start(source: number, ms: number): void
take(source: number, ms: number): boolean
left(source: number): number
clear(source: number): void
MetodaCo robiZwraca
ready(source)Dla gracza nie trwa żaden cooldown.boolean
start(source, ms)Zaczyna (albo zaczyna od nowa) cooldown gracza na ms milisekund.void
take(source, ms)Zaczyna cooldown, gdy jest gotowy.true, gdy zaczęty, false, dopóki jeden jeszcze trwa
left(source)Ile milisekund zostało.number, 0, gdy gotowy
clear(source)Kończy cooldown gracza.void
import { onClientCallback, Cooldowns } from "@aicore-fivem/lib/server";
import { fail } from "@aicore-fivem/lib/shared";

const searchCooldown = new Cooldowns();

onClientCallback("my-bins:search", (source) => {
  if (!searchCooldown.take(source, 30_000)) return fail("WAIT");
  return { ok: true };
});

Gracz może połączyć się ponownie, żeby zresetować wpis Cooldowns. Cooldown, który musi przetrwać ponowne połączenie, używa KeyedCooldowns kluczowanych przez online.charId(source).

KeyedCooldowns

Cooldowny według dowolnego klucza, który przetrwa połączenie: id postaci, encji, drzwi. Wygasłe wpisy są sprzątane w miarę wzrostu mapy.

Strona: Serwer · Czeka: nie

Sygnatura
new KeyedCooldowns<K = number>()
ready(key: K): boolean
start(key: K, ms: number): void
take(key: K, ms: number): boolean
left(key: K): number
clear(key: K): void

Te same metody co Cooldowns, według key. start z ms równym 0 lub mniej kończy cooldown.

import { KeyedCooldowns, online } from "@aicore-fivem/lib/server";
import { fail } from "@aicore-fivem/lib/shared";

const robbed = new KeyedCooldowns<string>(); // według id sklepu
const perCharacter = new KeyedCooldowns(); // według id postaci

function canRob(source: number, store: string) {
  const charId = online.charId(source);
  if (charId === undefined) return fail("NO_CHARACTER");
  if (!robbed.ready(store)) return fail("STORE_EMPTY");
  if (!perCharacter.take(charId, 15 * 60_000)) return fail("WAIT");
  robbed.start(store, 30 * 60_000);
  return { ok: true };
}

SourceLock

Jedna trwająca akcja na gracza w kilku callbackach. Własne lock: true callbacka obejmuje tylko ten callback; dwie akcje, które muszą się wzajemnie wykluczać (wystawienie i sprzedaż tego samego pojazdu), dzielą jeden SourceLock.

Strona: Serwer · Czeka: nie

Sygnatura
new SourceLock()
run<T, B>(source: number, busy: B, task: () => Promise<T>): Promise<T | B>
release(source: number): void

Prop

Type

Zwraca (run) wynik zadania albo busy. release(source) zwalnia gracza; wywołuj go z playerDropped (zadanie, które jeszcze trwa, trzyma własny wpis, aż się zakończy).

import { onClientCallback, SourceLock } from "@aicore-fivem/lib/server";
import { fail } from "@aicore-fivem/lib/shared";

const dealer = new SourceLock();
on("playerDropped", () => dealer.release(Number(globalThis.source)));

onClientCallback("my-dealer:list", (source) => dealer.run(source, fail("BUSY"), async () => ({ ok: true })));
onClientCallback("my-dealer:sell", (source) => dealer.run(source, fail("BUSY"), async () => ({ ok: true })));

RateLimit

Limit serii na gracza: najwyżej count akcji w dowolnym windowMs. Bramka już ogranicza częstotliwość każdego wejścia; używaj tego dla dodatkowego, wolniejszego limitu jednej akcji.

Strona: Serwer · Czeka: nie

Sygnatura
new RateLimit(count: number, windowMs: number)
take(source: number): boolean
release(source: number): void
Lua
local limit = aiLib.RateLimit.new(count, windowMs)
local allowed = limit:take(source)
limit:release(source)

Prop

Type

Zwraca (take) true i liczy jedną akcję albo false, gdy gracz przekroczył limit (nie liczone). release(source) zapomina gracza; wywołuj go z playerDropped.

import { onClientCallback, RateLimit } from "@aicore-fivem/lib/server";
import { fail } from "@aicore-fivem/lib/shared";

const reports = new RateLimit(3, 60_000);
on("playerDropped", () => reports.release(Number(globalThis.source)));

onClientCallback("my-report:send", (source) => {
  if (!reports.take(source)) return fail("TOO_MANY_REPORTS");
  return { ok: true };
});

KeyWatchers

Gracze, którzy dostają zmiany jednego klucza na żywo (samochody parkingu, otwarta karta postaci). Jeden klucz na gracza: obserwowanie nowego klucza kończy obserwowanie starego. Zwalniane przy wyjściu; każda operacja ma stały czas.

Strona: Serwer · Czeka: nie

Sygnatura
new KeyWatchers<K>()
watch(source: number, key: K): void
stop(source: number): void
of(key: K): ReadonlySet<number> | undefined
keyOf(source: number): K | undefined
MetodaCo robiZwraca
watch(source, key)Gracz obserwuje key i przestaje obserwować poprzedni.void
stop(source)Gracz przestaje obserwować.void
of(key)Gracze obserwujący key.zbiór albo undefined, gdy nikt nie obserwuje
keyOf(source)Klucz, który obserwuje gracz.klucz albo undefined
import { onClientCallback, onClientEvent, KeyWatchers } from "@aicore-fivem/lib/server";
import { oneArg, readText } from "@aicore-fivem/lib/shared";

const lots = new KeyWatchers<string>();

onClientCallback("my-lot:open", { args: oneArg((v) => readText(v, 32)) }, (source, lot) => {
  lots.watch(source, lot);
  return { ok: true, cars: [] }; // migawka
});
onClientEvent("my-lot:close", (source) => lots.stop(source));

function carChanged(lot: string, car: unknown) {
  for (const source of lots.of(lot) ?? []) emitNet("my-lot:car", source, car); // delta
}

Na tej stronie