AI Core
Referência da APILib: servidor

Jogadores e personagens

online, onCharacter, notify, jogadores próximos, posições e stores por jogador: PlayerMap, Cooldowns, KeyedCooldowns, SourceLock, RateLimit, KeyWatchers.

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

Um jogador é um cliente conectado com um id de servidor (source). Um personagem é a pessoa que ele interpreta; um jogador na tela de personagens não tem personagem carregado. O ai-core é dono dos jogadores e personagens; estes helpers os leem.

online

online é a cópia, no seu recurso, dos jogadores carregados do ai-core: personagens, vínculos com empregos e gangues com cargos, listas de membros por emprego e gangue, e serviço. Ele lê o ai-core uma vez no primeiro uso e depois acompanha os eventos do ai-core, então uma leitura é uma consulta em memória. Use em caminhos quentes (cada mensagem de chat, cada alerta) em vez de chamar os exports do ai-core.

Em Lua, aiLib.online consulta os exports do ai-core a cada chamada: bom para ações, não para todo tick. Ele tem character, name, jobs, gangs, jobGrade, gangGrade, dutyJob e sources; o resto não tem equivalente em Lua.

online.sources

Os ids de servidor de todos os jogadores com um personagem carregado.

Lado: Servidor · Espera: não

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

Retorno

IterableIterator<number> (Lua: um array). Jogadores na tela de personagens não estão nele.

Exemplo

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

Quantos jogadores têm um personagem carregado.

Lado: Servidor · Espera: não

Signature
readonly online.size: number

Sem equivalente em Lua: #aiLib.online.sources().

online.character

O personagem carregado de um jogador.

Lado: Servidor · Espera: não

Signature
online.character(source: number): CharacterInfo | null
Lua
local character = aiLib.online.character(source) -- table ou nil

Prop

Type

Retorno

CharacterInfo (id, citizenid, slot, firstname, lastname, birthdate, sex, nationality), ou null (nil em Lua) quando o jogador não tem personagem carregado.

Exemplo

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

online.charId

O id do personagem carregado do jogador (o id no banco de dados, estável entre reconexões).

Lado: Servidor · Espera: não

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

Sem equivalente em Lua: (aiLib.online.character(source) or {}).id.

Retorno o id do personagem, ou undefined quando nenhum está carregado. Indexe por este id, não por source, os dados que precisam sobreviver a uma reconexão (cooldowns, posse).

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

online.name

O "Nome Sobrenome" do personagem, ou o nome de jogador do FiveM quando nenhum personagem está carregado.

Lado: Servidor · Espera: não

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

Prop

Type

Retorno string.

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

online.jobs

Todos os empregos de que o jogador é membro, em serviço ou não. O ai-core permite que um personagem tenha vários empregos (maxJobs no painel), com um emprego em serviço por vez.

Lado: Servidor · Espera: não

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

Retorno os nomes dos empregos (vazio quando não há nenhum ou não há personagem). O equivalente em Lua retorna tabelas de vínculo { job, grade }, não nomes.

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

online.gangs

Todas as gangues em que o jogador está.

Lado: Servidor · Espera: não

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

Retorno os nomes das gangues (vazio quando não há nenhuma). O equivalente em Lua retorna tabelas de vínculo { gang, grade }.

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

online.jobGrade

O cargo do jogador em um emprego.

Lado: Servidor · Espera: não

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

Prop

Type

Retorno o número do cargo, ou null (nil em Lua) quando o jogador não é membro. O cargo 0 é um membro: compare com null, não com uma verificação de valor verdadeiro.

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

online.gangGrade

O cargo do jogador em uma gangue.

Lado: Servidor · Espera: não

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

Retorno o cargo, ou null (nil em Lua) quando não é membro.

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

online.jobMembers

Todos os jogadores com vínculo em um emprego, em serviço ou não.

Lado: Servidor · Espera: não

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

Sem equivalente em Lua.

Retorno um set vivo de ids de servidor (vazio quando não há nenhum). Ele muda conforme os jogadores entram e saem do emprego: copie-o ([...set]) antes de um await.

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

online.gangMembers

Todos os jogadores de uma gangue.

Lado: Servidor · Espera: não

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

Sem equivalente em Lua. Retorno um set vivo de ids de servidor; copie-o antes de um await.

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

online.dutyJob

O emprego em que o jogador está em serviço.

Lado: Servidor · Espera: não

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

Retorno o nome do emprego, ou null (nil em Lua) quando fora de serviço.

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

online.onDutyIn

Se o jogador está em serviço em um de vários empregos (todos os empregos de polícia, todos os de EMS).

Lado: Servidor · Espera: não

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

Sem equivalente em Lua. Retorno true quando o emprego em serviço está em jobs.

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

online.onDutyFor

Os jogadores em serviço em um emprego.

Lado: Servidor · Espera: não

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

Sem equivalente em Lua. Retorno um set vivo de ids de servidor; copie-o antes de um await.

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

online.countOnDuty

Quantos jogadores estão em serviço somando vários empregos.

Lado: Servidor · Espera: não

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

Sem equivalente em Lua. Retorno a contagem total (o custo cresce com o número de empregos, não de jogadores).

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

online.allows

Se o jogador pode usar um lugar travado por uma regra de acesso (empregos, gangues, cargos mínimos, em serviço). O allows do cliente usa a mesma regra, então o que um jogador vê é o que o servidor o deixa usar.

Lado: Servidor · Espera: não

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

Sem equivalente em Lua.

Prop

Type

Retorno true quando as listas estão vazias, o jogador está em uma das gangs no cargo mínimo dela, ou em um dos jobs no cargo mínimo dele (e em serviço nele quando onDuty está definido).

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");

Ciclo de vida do personagem

onCharacter

Roda o seu código quando um personagem entra no mundo e quando sai. Também repete os personagens que já estão no mundo quando o seu recurso (re)inicia, e cobre as saídas, então você não precisa de um handler de playerDropped ao lado dele.

Lado: Servidor · Espera: não

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

CharacterHandlers

Prop

Type

Retorno

void.

Exemplo

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);
  },
});

Chame uma vez no nível superior de um arquivo de servidor. O PlayerMap acima é esvaziado sozinho na saída; o handler de unloaded ainda roda primeiro em uma saída, então você pode salvar dados ali.

Falando com os jogadores

notify

Mostra um card de notificação na tela de um jogador (o ai-lib o desenha, ou a UI de notificação que o dono escolheu no painel do ai-core).

Lado: Servidor · Espera: não

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

Parâmetros

Prop

Type

Retorno

void.

Exemplo

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

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

Para responder a uma ação do cliente, retorne fail("KEY") em vez de chamar notify: o callAction do cliente mostra o erro traduzido sozinho.

isOnline

Se um jogador ainda está conectado. Verifique depois de um await longo antes de dar qualquer coisa.

Lado: Servidor · Espera: não

Signature
isOnline(source: number): boolean

Sem equivalente em Lua: GetPlayerName(source) ~= nil.

Retorno true enquanto o jogador está conectado.

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

Jogadores próximos

Estes perguntam ao ai-core quais jogadores estão no escopo OneSync uns dos outros (cerca de 424 metros) e medem só esses poucos, em vez de percorrer todos os jogadores do servidor. Um raio maior que cerca de 424 m não encontra ninguém mais longe.

playersNear

Os jogadores a até radius metros de um jogador.

Lado: Servidor · Espera: não

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

Prop

Type

Retorno um array de ids de servidor. O TypeScript inclui o próprio source; o Lua o deixa de fora.

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

playersNearEntity

Os jogadores a até radius metros de uma entidade em rede (um veículo, um prop): o dono dela e os jogadores no escopo do dono.

Lado: Servidor · Espera: não

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

Prop

Type

Retorno um array de ids de servidor. Vazio quando a entidade não existe ou nenhum jogador é dono dela (uma entidade de que só o servidor é dono não tem ninguém perto).

const witnesses = playersNearEntity(vehicle, 30);

nearestPlayerTo

O outro jogador mais próximo, a até metres de um jogador, que passa no seu filtro.

Lado: Servidor · Espera: não

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

Prop

Type

Retorno o id de servidor do mais próximo que corresponde, ou null (nil em Lua) quando não há nenhum.

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

Posições

Sem equivalentes em Lua: Lua tem matemática de vetores (GetEntityCoords(GetPlayerPed(source)), #(a - b)).

coordsOf

A posição do ped de um jogador como o servidor a vê.

Lado: Servidor · Espera: não

Signature
coordsOf(source: number): Vec3

Retorno um Vec3 { x, y, z }.

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

entityCoords

A posição de qualquer entidade (a cópia no servidor da sincronização do dono dela).

Lado: Servidor · Espera: não

Signature
entityCoords(entity: number): Vec3

Retorno um Vec3.

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

isNear

Se o ped do jogador está a até metres de um ponto. O guard near o usa.

Lado: Servidor · Espera: não

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

Prop

Type

Retorno true quando está ao alcance.

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

vehicleOf

O veículo em que o jogador está sentado.

Lado: Servidor · Espera: não

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

Prop

Type

Retorno a entidade do veículo, ou 0 a pé (ou fora do banco do motorista com driver: true).

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

Stores por jogador

Nenhum destes tem equivalente em Lua: use uma tabela Lua e limpe-a em playerDropped. Crie cada store uma vez no nível do módulo, nunca dentro de um handler.

PlayerMap

Um Map indexado pelo id de servidor do jogador que se esvazia sozinho quando o jogador sai. Use para tudo o que você guarda por jogador (uma tela aberta, um item selecionado).

Lado: Servidor · Espera: não

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

Prop

Type

Ele tem todos os métodos de um Map do JavaScript (get, set, has, delete, size, iteração).

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

Cooldowns por jogador no relógio do jogo, liberados quando o jogador sai.

Lado: Servidor · Espera: não

Signature
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
MétodoO que fazRetorno
ready(source)Nenhum cooldown está rodando para o jogador.boolean
start(source, ms)Inicia (ou reinicia) o cooldown do jogador por ms milissegundos.void
take(source, ms)Inicia o cooldown quando ele está pronto.true quando iniciado, false enquanto um ainda roda
left(source)Milissegundos restantes.number, 0 quando pronto
clear(source)Encerra o cooldown do jogador.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 };
});

Um jogador pode reconectar para zerar uma entrada de Cooldowns. Um cooldown que precisa sobreviver a uma reconexão usa KeyedCooldowns indexado por online.charId(source).

KeyedCooldowns

Cooldowns por qualquer chave que dura mais que uma conexão: um id de personagem, uma entidade, um id de porta. Entradas expiradas são limpas conforme o mapa cresce.

Lado: Servidor · Espera: não

Signature
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

Os mesmos métodos de Cooldowns, por key. start com ms de 0 ou menos encerra o cooldown.

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

const robbed = new KeyedCooldowns<string>(); // por id de loja
const perCharacter = new KeyedCooldowns(); // por id de personagem

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

Uma ação em andamento por jogador entre vários callbacks. O próprio lock: true de um callback cobre só aquele callback; duas ações que precisam se excluir (listar e vender o mesmo veículo) compartilham um SourceLock.

Lado: Servidor · Espera: não

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

Prop

Type

Retorno (run) o resultado da tarefa, ou busy. release(source) libera o jogador; chame a partir de playerDropped (uma tarefa ainda rodando mantém a própria entrada até terminar).

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

Um limite de rajada por jogador: no máximo count ações dentro de qualquer windowMs. O portão já limita a taxa de toda entrada; use isto para um limite extra, mais lento, em uma ação.

Lado: Servidor · Espera: não

Signature
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

Retorno (take) true e conta uma ação, ou false quando o jogador passou do limite (não contada). release(source) esquece o jogador; chame a partir de 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

Os jogadores que recebem as mudanças ao vivo de uma chave (os carros de um estacionamento, uma ficha de personagem aberta). Uma chave por jogador: observar uma nova chave para a antiga. Liberado na saída; toda operação é de tempo constante.

Lado: Servidor · Espera: não

Signature
new KeyWatchers<K>()
watch(source: number, key: K): void
stop(source: number): void
of(key: K): ReadonlySet<number> | undefined
keyOf(source: number): K | undefined
MétodoO que fazRetorno
watch(source, key)O jogador observa key e para de observar a anterior.void
stop(source)O jogador para de observar.void
of(key)Os jogadores que observam key.um set, ou undefined quando ninguém observa
keyOf(source)A chave que o jogador observa.a chave, ou 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: [] }; // o snapshot
});
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); // o delta
}

Nesta página