AI Core
Referencia de la APILib: servidor

Jugadores y personajes

online, onCharacter, notify, jugadores cercanos, posiciones y almacenes por jugador: PlayerMap, Cooldowns, KeyedCooldowns, SourceLock, RateLimit, KeyWatchers.

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

Un jugador es un cliente conectado con un id de servidor (source). Un personaje es la persona que interpreta; un jugador en la pantalla de personajes no tiene ningún personaje cargado. ai-core es el dueño de los jugadores y los personajes; estas utilidades los leen.

online

online es la copia que tiene tu recurso de los jugadores cargados de ai-core: personajes, pertenencias a trabajos y bandas con sus rangos, listas de miembros por trabajo y banda, y servicio. Lee ai-core una vez en el primer uso y luego sigue los eventos de ai-core, así que una lectura es una consulta en memoria. Úsalo en rutas calientes (cada mensaje de chat, cada alerta) en lugar de llamar a los exports de ai-core.

En Lua, aiLib.online pregunta a los exports de ai-core en cada llamada: está bien para acciones, no para cada tick. Tiene character, name, jobs, gangs, jobGrade, gangGrade, dutyJob y sources; el resto no tiene equivalente en Lua.

online.sources

Los ids de servidor de todos los jugadores con un personaje cargado.

Lado: Servidor · Espera: no

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

Devuelve

IterableIterator<number> (Lua: un array). Los jugadores en la pantalla de personajes no están en él.

Ejemplo

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

Cuántos jugadores tienen un personaje cargado.

Lado: Servidor · Espera: no

Firma
readonly online.size: number

Sin equivalente en Lua: #aiLib.online.sources().

online.character

El personaje cargado de un jugador.

Lado: Servidor · Espera: no

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

Prop

Type

Devuelve

CharacterInfo (id, citizenid, slot, firstname, lastname, birthdate, sex, nationality), o null (nil en Lua) cuando el jugador no tiene ningún personaje cargado.

Ejemplo

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

online.charId

El id del personaje cargado del jugador (el id de la base de datos, estable entre reconexiones).

Lado: Servidor · Espera: no

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

Sin equivalente en Lua: (aiLib.online.character(source) or {}).id.

Devuelve el id del personaje, o undefined cuando no hay ninguno cargado. Indexa por este id, no por source, los datos que deben sobrevivir a una reconexión (cooldowns, propiedad).

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

online.name

El "Nombre Apellido" del personaje, o el nombre de jugador de FiveM cuando no hay ningún personaje cargado.

Lado: Servidor · Espera: no

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

Prop

Type

Devuelve string.

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

online.jobs

Todos los trabajos de los que el jugador es miembro, esté de servicio o no. ai-core permite que un personaje tenga varios trabajos (maxJobs en el panel) con un único trabajo de servicio a la vez.

Lado: Servidor · Espera: no

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

Devuelve los nombres de los trabajos (vacío cuando no hay ninguno o no hay personaje). El equivalente en Lua devuelve tablas de pertenencia { job, grade }, no nombres.

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

online.gangs

Todas las bandas en las que está el jugador.

Lado: Servidor · Espera: no

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

Devuelve los nombres de las bandas (vacío cuando no hay ninguna). El equivalente en Lua devuelve tablas de pertenencia { gang, grade }.

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

online.jobGrade

El rango del jugador en un trabajo.

Lado: Servidor · Espera: no

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

Prop

Type

Devuelve el número de rango, o null (nil en Lua) cuando el jugador no es miembro. El rango 0 es un miembro: compara con null, no con una comprobación de verdad.

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

online.gangGrade

El rango del jugador en una banda.

Lado: Servidor · Espera: no

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

Devuelve el rango, o null (nil en Lua) cuando no es miembro.

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

online.jobMembers

Todos los jugadores con una pertenencia a un trabajo, estén de servicio o no.

Lado: Servidor · Espera: no

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

Sin equivalente en Lua.

Devuelve un set vivo de ids de servidor (vacío cuando no hay ninguno). Cambia a medida que los jugadores entran y salen del trabajo: cópialo ([...set]) antes de un await.

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

online.gangMembers

Todos los jugadores de una banda.

Lado: Servidor · Espera: no

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

Sin equivalente en Lua. Devuelve un set vivo de ids de servidor; cópialo antes de un await.

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

online.dutyJob

El trabajo para el que el jugador está de servicio.

Lado: Servidor · Espera: no

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

Devuelve el nombre del trabajo, o null (nil en Lua) cuando está fuera de servicio.

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

online.onDutyIn

Si el jugador está de servicio en uno de varios trabajos (todos los trabajos de policía, todos los de EMS).

Lado: Servidor · Espera: no

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

Sin equivalente en Lua. Devuelve true cuando el trabajo de servicio está en jobs.

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

online.onDutyFor

Los jugadores de servicio en un trabajo.

Lado: Servidor · Espera: no

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

Sin equivalente en Lua. Devuelve un set vivo de ids de servidor; cópialo antes de un await.

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

online.countOnDuty

Cuántos jugadores están de servicio sumando varios trabajos.

Lado: Servidor · Espera: no

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

Sin equivalente en Lua. Devuelve el recuento total (el coste crece con el número de trabajos, no de jugadores).

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

online.allows

Si el jugador puede usar un lugar cerrado por una regla de acceso (trabajos, bandas, rangos mínimos, de servicio). allows del cliente usa la misma regla, así que lo que ve un jugador es lo que el servidor le deja usar.

Lado: Servidor · Espera: no

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

Sin equivalente en Lua.

Prop

Type

Devuelve true cuando las listas están vacías, el jugador está en una de las gangs con su rango mínimo, o en uno de los jobs con su rango mínimo (y de servicio en él cuando onDuty está activado).

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 del personaje

onCharacter

Ejecuta tu código cuando un personaje entra en el mundo y cuando sale. También reproduce los personajes que ya están en el mundo cuando tu recurso (re)arranca, y cubre las desconexiones, así que no necesitas un handler de playerDropped junto a él.

Lado: Servidor · Espera: no

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

CharacterHandlers

Prop

Type

Devuelve

void.

Ejemplo

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

Llámalo una vez en el nivel superior de un archivo de servidor. El PlayerMap de arriba se vacía solo al desconectarse; el handler de unloaded se sigue ejecutando primero en una desconexión, así que puedes guardar datos ahí.

Hablar con los jugadores

notify

Muestra una tarjeta de notificación en la pantalla de un jugador (la dibuja ai-lib, o la interfaz de notificaciones que el dueño eligió en el panel de ai-core).

Lado: Servidor · Espera: no

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

Parámetros

Prop

Type

Devuelve

void.

Ejemplo

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

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

Para responder a una acción del cliente, devuelve fail("KEY") en lugar de llamar a notify: callAction del cliente muestra el error traducido por sí mismo.

isOnline

Si un jugador sigue conectado. Compruébalo después de un await largo, antes de dar nada.

Lado: Servidor · Espera: no

Firma
isOnline(source: number): boolean

Sin equivalente en Lua: GetPlayerName(source) ~= nil.

Devuelve true mientras el jugador está conectado.

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

Jugadores cercanos

Estas funciones preguntan a ai-core qué jugadores están en el alcance OneSync de los otros (unos 424 metros) y miden solo esos pocos, en lugar de recorrer todos los jugadores del servidor. Un radio mayor de unos 424 m no encuentra a nadie más lejos.

playersNear

Los jugadores a menos de radius metros de un jugador.

Lado: Servidor · Espera: no

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

Prop

Type

Devuelve un array de ids de servidor. TypeScript incluye al propio source; Lua lo deja fuera.

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

playersNearEntity

Los jugadores a menos de radius metros de una entidad en red (un vehículo, un prop): su dueño y los jugadores en el alcance del dueño.

Lado: Servidor · Espera: no

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

Prop

Type

Devuelve un array de ids de servidor. Vacío cuando la entidad no existe o ningún jugador es su dueño (una entidad que solo posee el servidor no tiene a nadie cerca).

const witnesses = playersNearEntity(vehicle, 30);

nearestPlayerTo

El otro jugador más cercano a menos de metres de un jugador que pasa tu filtro.

Lado: Servidor · Espera: no

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

Prop

Type

Devuelve el id de servidor de la coincidencia más cercana, o null (nil en Lua) cuando no hay ninguna.

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

Posiciones

Sin equivalentes en Lua: Lua tiene matemáticas de vectores (GetEntityCoords(GetPlayerPed(source)), #(a - b)).

coordsOf

La posición del ped de un jugador tal como la ve el servidor.

Lado: Servidor · Espera: no

Firma
coordsOf(source: number): Vec3

Devuelve un Vec3 { x, y, z }.

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

entityCoords

La posición de cualquier entidad (la copia en el servidor de la sincronización de su dueño).

Lado: Servidor · Espera: no

Firma
entityCoords(entity: number): Vec3

Devuelve un Vec3.

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

isNear

Si el ped del jugador está a menos de metres de un punto. La guarda near lo usa.

Lado: Servidor · Espera: no

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

Prop

Type

Devuelve true cuando está a su alcance.

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

vehicleOf

El vehículo en el que está sentado el jugador.

Lado: Servidor · Espera: no

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

Prop

Type

Devuelve la entidad del vehículo, o 0 a pie (o si no está en el asiento del conductor con driver: true).

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

Almacenes por jugador

Ninguno de estos tiene equivalente en Lua: usa una tabla Lua y vacíala en playerDropped. Crea cada almacén una vez a nivel de módulo, nunca dentro de un handler.

PlayerMap

Un Map indexado por id de servidor del jugador que se vacía solo cuando el jugador se desconecta. Úsalo para todo lo que guardes por jugador (una pantalla abierta, un objeto seleccionado).

Lado: Servidor · Espera: no

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

Prop

Type

Tiene todos los métodos de un Map de JavaScript (get, set, has, delete, size, iteración).

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 jugador sobre el reloj del juego, liberados cuando el jugador se desconecta.

Lado: Servidor · Espera: no

Firma
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étodoQué haceDevuelve
ready(source)No hay ningún cooldown en marcha para el jugador.boolean
start(source, ms)Inicia (o reinicia) el cooldown del jugador durante ms milisegundos.void
take(source, ms)Inicia el cooldown cuando está listo.true cuando se inició, false mientras uno sigue en marcha
left(source)Milisegundos restantes.number, 0 cuando está listo
clear(source)Termina el cooldown del jugador.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 };
});

Un jugador puede reconectarse para reiniciar una entrada de Cooldowns. Un cooldown que deba sobrevivir a una reconexión usa KeyedCooldowns indexado por online.charId(source).

KeyedCooldowns

Cooldowns por cualquier clave que sobreviva a una conexión: un id de personaje, una entidad, un id de puerta. Las entradas caducadas se limpian a medida que crece el mapa.

Lado: Servidor · Espera: no

Firma
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

Los mismos métodos que Cooldowns, por key. start con ms de 0 o menos termina el cooldown.

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

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

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

Una acción en marcha por jugador a través de varios callbacks. El propio lock: true de un callback solo cubre ese callback; dos acciones que deben excluirse entre sí (poner a la venta y vender el mismo vehículo) comparten un SourceLock.

Lado: Servidor · Espera: no

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

Prop

Type

Devuelve (run) el resultado de la tarea, o busy. release(source) libera al jugador; llámalo desde playerDropped (una tarea que sigue en marcha conserva su propia entrada hasta que termina).

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

Un límite de ráfaga por jugador: como máximo count acciones dentro de cualquier ventana de windowMs. La puerta ya limita la frecuencia de cada entrada; usa esto para un límite extra, más lento, en una acción.

Lado: Servidor · Espera: no

Firma
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

Devuelve (take) true y cuenta una acción, o false cuando el jugador supera el límite (no se cuenta). release(source) olvida al jugador; llámalo desde 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

Los jugadores que reciben los cambios en vivo de una clave (los coches de un aparcamiento, una ficha de personaje abierta). Una clave por jugador: observar una clave nueva detiene la anterior. Se libera al desconectarse; cada operación es de tiempo constante.

Lado: Servidor · Espera: no

Firma
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étodoQué haceDevuelve
watch(source, key)El jugador observa key y deja de observar la anterior.void
stop(source)El jugador deja de observar.void
of(key)Los jugadores que observan key.un set, o undefined cuando nadie observa
keyOf(source)La clave que observa el jugador.la clave, o 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: [] }; // el 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); // el delta
}

En esta página