Gracze i postacie
online, onCharacter, notify, gracze w pobliżu, pozycje i magazyny na gracza: PlayerMap, Cooldowns, KeyedCooldowns, SourceLock, RateLimit, KeyWatchers.
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
online.sources(): IterableIterator<number>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
readonly online.size: numberBrak odpowiednika w Lua: #aiLib.online.sources().
online.character
Wczytana postać gracza.
Strona: Serwer · Czeka: nie
online.character(source: number): CharacterInfo | nulllocal character = aiLib.online.character(source) -- tabela albo nilProp
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
online.charId(source: number): number | undefinedBrak 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
online.name(source: number, options?: { fallback?: boolean }): stringlocal 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
online.jobs(source: number): readonly string[]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
online.gangs(source: number): readonly string[]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
online.jobGrade(source: number, job: string): number | nulllocal grade = aiLib.online.jobGrade(source, job) -- integer albo nilProp
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
online.gangGrade(source: number, gang: string): number | nulllocal grade = aiLib.online.gangGrade(source, gang) -- integer albo nilZwraca 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
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
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
online.dutyJob(source: number): string | nulllocal job = aiLib.online.dutyJob(source) -- string albo nilZwraca 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
online.onDutyIn(source: number, jobs: readonly string[]): booleanBrak 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
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
online.countOnDuty(jobs: readonly string[]): numberBrak 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
online.allows(source: number, rule: AccessRule): booleanBrak 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
onCharacter(handlers: CharacterHandlers): voidaiLib.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
notify(source: number, text: string, kind?: NotificationKind, duration?: number): voidaiLib.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
isOnline(source: number): booleanBrak 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
playersNear(source: number, radius: number): number[]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
playersNearEntity(entity: number, radius: number): number[]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
nearestPlayerTo(source: number, metres: number, filter?: (other: number) => boolean): number | nulllocal other = aiLib.nearestPlayerTo(source, metres, filter) -- integer albo nilProp
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
coordsOf(source: number): Vec3Zwraca 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
entityCoords(entity: number): Vec3Zwraca 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
isNear(source: number, point: Vec3, metres: number): booleanProp
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
vehicleOf(source: number, options?: { driver?: boolean }): numberProp
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
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
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| Metoda | Co robi | Zwraca |
|---|---|---|
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
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): voidTe 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
new SourceLock()
run<T, B>(source: number, busy: B, task: () => Promise<T>): Promise<T | B>
release(source: number): voidProp
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
new RateLimit(count: number, windowMs: number)
take(source: number): boolean
release(source: number): voidlocal 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
new KeyWatchers<K>()
watch(source: number, key: K): void
stop(source: number): void
of(key: K): ReadonlySet<number> | undefined
keyOf(source: number): K | undefined| Metoda | Co robi | Zwraca |
|---|---|---|
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
}