Joueurs et personnages
online, onCharacter, notify, joueurs proches, positions et stockages par joueur : PlayerMap, Cooldowns, KeyedCooldowns, SourceLock, RateLimit, KeyWatchers.
import { online, onCharacter, notify, playersNear, coordsOf, PlayerMap, Cooldowns } from "@aicore-fivem/lib/server";Un joueur est un client connecté qui a un id serveur (source). Un personnage est la personne qu’il incarne ; un joueur sur l’écran de sélection de personnage n’a aucun personnage chargé. ai-core possède les joueurs et les personnages ; ces helpers les lisent.
online
online est la copie, dans votre ressource, des joueurs chargés d’ai-core : personnages, appartenances aux métiers et aux gangs avec leurs grades, listes de membres par métier et par gang, et service. Elle lit ai-core une fois à la première utilisation puis suit les événements d’ai-core, donc une lecture est une simple consultation en mémoire. Utilisez-la dans les chemins critiques (chaque message de chat, chaque alerte) au lieu d’appeler les exports d’ai-core.
En Lua, aiLib.online interroge les exports d’ai-core à chaque appel : convenable pour des actions, pas pour chaque tick. Elle propose character, name, jobs, gangs, jobGrade, gangGrade, dutyJob et sources ; le reste n’a pas d’équivalent Lua.
online.sources
Les ids serveur de tous les joueurs qui ont un personnage chargé.
Côté : serveur · Attend : non
online.sources(): IterableIterator<number>local list = aiLib.online.sources() -- integer[]Retour
IterableIterator<number> (Lua : un tableau). Les joueurs sur l’écran de sélection de personnage n’y figurent pas.
Exemple
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
Combien de joueurs ont un personnage chargé.
Côté : serveur · Attend : non
readonly online.size: numberPas d’équivalent Lua : #aiLib.online.sources().
online.character
Le personnage chargé d’un joueur.
Côté : serveur · Attend : non
online.character(source: number): CharacterInfo | nulllocal character = aiLib.online.character(source) -- table ou nilProp
Type
Retour
CharacterInfo (id, citizenid, slot, firstname, lastname, birthdate, sex, nationality), ou null (Lua nil) quand le joueur n’a aucun personnage chargé.
Exemple
const character = online.character(source);
if (character) console.log(`${character.citizenid} is ${character.firstname}`);online.charId
L’id du personnage chargé du joueur (l’id en base de données, stable d’une reconnexion à l’autre).
Côté : serveur · Attend : non
online.charId(source: number): number | undefinedPas d’équivalent Lua : (aiLib.online.character(source) or {}).id.
Retour l’id du personnage, ou undefined quand aucun n’est chargé. Indexez par cet id, et non par source, les données qui doivent survivre à une reconnexion (cooldowns, propriété).
const charId = online.charId(source);
if (charId === undefined) return fail("NO_CHARACTER");online.name
Le « Prénom Nom » du personnage, ou le nom de joueur FiveM quand aucun personnage n’est chargé.
Côté : serveur · Attend : non
online.name(source: number, options?: { fallback?: boolean }): stringlocal name = aiLib.online.name(source, fallback) -- fallback: boolean?Prop
Type
Retour string.
notify(target, `${online.name(source)} sent you a gift`, "success");online.jobs
Tous les métiers dont le joueur est membre, en service ou non. ai-core permet à un personnage d’avoir plusieurs métiers (maxJobs dans le panneau) avec un seul métier en service à la fois.
Côté : serveur · Attend : non
online.jobs(source: number): readonly string[]local memberships = aiLib.online.jobs(source) -- { { job = 'police', grade = 2 }, ... }Retour les noms des métiers (vide quand il n’y en a aucun ou sans personnage). L’équivalent Lua renvoie des tables d’appartenance { job, grade }, pas des noms.
const isMechanic = online.jobs(source).includes("mechanic");online.gangs
Tous les gangs dont fait partie le joueur.
Côté : serveur · Attend : non
online.gangs(source: number): readonly string[]local memberships = aiLib.online.gangs(source) -- { { gang = 'ballas', grade = 0 }, ... }Retour les noms des gangs (vide quand il n’y en a aucun). L’équivalent Lua renvoie des tables d’appartenance { gang, grade }.
if (online.gangs(source).length === 0) return fail("NOT_IN_GANG");online.jobGrade
Le grade du joueur dans un métier.
Côté : serveur · Attend : non
online.jobGrade(source: number, job: string): number | nulllocal grade = aiLib.online.jobGrade(source, job) -- entier ou nilProp
Type
Retour le numéro de grade, ou null (Lua nil) quand le joueur n’est pas membre. Le grade 0 est un membre : comparez avec null, pas avec un test de vérité.
const grade = online.jobGrade(source, "police");
if (grade === null || grade < 3) return fail("RANK_TOO_LOW");online.gangGrade
Le grade du joueur dans un gang.
Côté : serveur · Attend : non
online.gangGrade(source: number, gang: string): number | nulllocal grade = aiLib.online.gangGrade(source, gang) -- entier ou nilRetour le grade, ou null (Lua nil) quand le joueur n’est pas membre.
const boss = (online.gangGrade(source, "ballas") ?? -1) >= 4;online.jobMembers
Tous les joueurs membres d’un métier, en service ou non.
Côté : serveur · Attend : non
online.jobMembers(job: string): ReadonlySet<number>Pas d’équivalent Lua.
Retour un ensemble vivant d’ids serveur (vide quand il n’y en a aucun). Il change au fil des entrées et sorties du métier : copiez-le ([...set]) avant un await.
for (const source of online.jobMembers("mechanic")) notify(source, "A customer is waiting", "info");online.gangMembers
Tous les joueurs d’un gang.
Côté : serveur · Attend : non
online.gangMembers(gang: string): ReadonlySet<number>Pas d’équivalent Lua. Retour un ensemble vivant d’ids serveur ; copiez-le avant un await.
const members = [...online.gangMembers("ballas")];online.dutyJob
Le métier pour lequel le joueur est en service.
Côté : serveur · Attend : non
online.dutyJob(source: number): string | nulllocal job = aiLib.online.dutyJob(source) -- chaîne ou nilRetour le nom du métier, ou null (Lua nil) hors service.
if (online.dutyJob(source) !== "ambulance") return fail("NOT_ON_DUTY");online.onDutyIn
Si le joueur est en service pour l’un de plusieurs métiers (tous les métiers de police, tous les métiers d’EMS).
Côté : serveur · Attend : non
online.onDutyIn(source: number, jobs: readonly string[]): booleanPas d’équivalent Lua. Retour true quand le métier en service figure dans jobs.
const POLICE = ["police", "sheriff"];
if (!online.onDutyIn(source, POLICE)) return fail("NOT_POLICE");online.onDutyFor
Les joueurs en service pour un métier.
Côté : serveur · Attend : non
online.onDutyFor(job: string): ReadonlySet<number>Pas d’équivalent Lua. Retour un ensemble vivant d’ids serveur ; copiez-le avant un await.
for (const medic of online.onDutyFor("ambulance")) notify(medic, "Patient at Pillbox", "info");online.countOnDuty
Combien de joueurs sont en service, tous métiers indiqués confondus.
Côté : serveur · Attend : non
online.countOnDuty(jobs: readonly string[]): numberPas d’équivalent Lua. Retour le total (le coût croît avec le nombre de métiers, pas de joueurs).
if (online.countOnDuty(["police", "sheriff"]) < 2) return fail("NOT_ENOUGH_POLICE");online.allows
Si le joueur peut utiliser un lieu verrouillé par une règle d’accès (métiers, gangs, grades minimaux, service). Le allows côté client utilise la même règle, donc ce qu’un joueur voit correspond à ce que le serveur le laisse utiliser.
Côté : serveur · Attend : non
online.allows(source: number, rule: AccessRule): booleanPas d’équivalent Lua.
Prop
Type
Retour true quand les listes sont vides, que le joueur fait partie de l’un des gangs avec son grade minimal, ou de l’un des jobs avec son grade minimal (et en service pour ce métier quand onDuty est défini).
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");Cycle de vie du personnage
onCharacter
Exécute votre code quand un personnage entre dans le monde et quand il le quitte. Elle rejoue aussi les personnages déjà présents dans le monde quand votre ressource (re)démarre, et couvre les déconnexions, vous n’avez donc besoin d’aucun gestionnaire playerDropped à côté.
Côté : serveur · Attend : non
onCharacter(handlers: CharacterHandlers): voidaiLib.onCharacter({ loaded = function(source, character) end, unloaded = function(source, charId, reason) end })CharacterHandlers
Prop
Type
Retour
void.
Exemple
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);
},
});Appelez-la une seule fois au niveau supérieur d’un fichier serveur. Le PlayerMap ci-dessus se vide de lui-même à la déconnexion ; le gestionnaire unloaded s’exécute quand même en premier lors d’une déconnexion, vous pouvez donc y sauvegarder des données.
Parler aux joueurs
notify
Affiche une carte de notification sur l’écran d’un joueur (dessinée par ai-lib, ou par l’interface de notification que le propriétaire a choisie dans le panneau d’ai-core).
Côté : serveur · Attend : non
notify(source: number, text: string, kind?: NotificationKind, duration?: number): voidaiLib.notify(source, text, kind, duration)Paramètres
Prop
Type
Retour
void.
Exemple
import { notify, createLocale } from "@aicore-fivem/lib/server";
const Locale = createLocale();
notify(source, Locale("PAID", 250), "success");Pour répondre à une action client, renvoyez fail("KEY") au lieu d’appeler notify : le callAction du client affiche lui-même l’erreur traduite.
isOnline
Si un joueur est toujours connecté. Vérifiez-le après un long await avant de donner quoi que ce soit.
Côté : serveur · Attend : non
isOnline(source: number): booleanPas d’équivalent Lua : GetPlayerName(source) ~= nil.
Retour true tant que le joueur est connecté.
await new Promise((resolve) => setTimeout(resolve, 60_000));
if (!isOnline(source)) return;Joueurs proches
Ces fonctions demandent à ai-core quels joueurs se trouvent dans le scope OneSync les uns des autres (environ 424 mètres) et ne mesurent que ces quelques joueurs, au lieu de parcourir tous les joueurs du serveur. Un rayon supérieur à environ 424 m ne trouve personne au-delà.
playersNear
Les joueurs situés à moins de radius mètres d’un joueur.
Côté : serveur · Attend : non
playersNear(source: number, radius: number): number[]local list = aiLib.playersNear(source, radius) -- integer[]Prop
Type
Retour un tableau d’ids serveur. En TypeScript, il inclut source lui-même ; en Lua, il l’exclut.
for (const other of playersNear(source, 20)) if (other !== source) notify(other, "You hear a scream", "warning");playersNearEntity
Les joueurs situés à moins de radius mètres d’une entité réseau (un véhicule, un prop) : son propriétaire et les joueurs dans le scope de celui-ci.
Côté : serveur · Attend : non
playersNearEntity(entity: number, radius: number): number[]local list = aiLib.playersNearEntity(entity, radius) -- integer[]Prop
Type
Retour un tableau d’ids serveur. Vide quand l’entité n’existe pas ou qu’aucun joueur n’en est propriétaire (une entité dont seul le serveur est propriétaire n’a personne autour d’elle).
const witnesses = playersNearEntity(vehicle, 30);nearestPlayerTo
L’autre joueur le plus proche, à moins de metres d’un joueur, qui passe votre filtre.
Côté : serveur · Attend : non
nearestPlayerTo(source: number, metres: number, filter?: (other: number) => boolean): number | nulllocal other = aiLib.nearestPlayerTo(source, metres, filter) -- entier ou nilProp
Type
Retour l’id serveur du joueur correspondant le plus proche, ou null (Lua nil) s’il n’y en a aucun.
const target = nearestPlayerTo(source, 2, (other) => online.character(other) !== null);
if (target === null) return fail("NOBODY_NEAR");Positions
Pas d’équivalents Lua : Lua dispose des calculs vectoriels (GetEntityCoords(GetPlayerPed(source)), #(a - b)).
coordsOf
La position du ped d’un joueur telle que le serveur la voit.
Côté : serveur · Attend : non
coordsOf(source: number): Vec3Retour un Vec3 { x, y, z }.
const { x, y, z } = coordsOf(source);entityCoords
La position de n’importe quelle entité (la copie serveur de la synchronisation de son propriétaire).
Côté : serveur · Attend : non
entityCoords(entity: number): Vec3Retour un Vec3.
import { distance } from "@aicore-fivem/lib/shared";
if (distance(entityCoords(vehicle), coordsOf(source)) > 5) return fail("TOO_FAR");isNear
Si le ped du joueur se trouve à moins de metres d’un point. La garde near l’utilise.
Côté : serveur · Attend : non
isNear(source: number, point: Vec3, metres: number): booleanProp
Type
Retour true quand le point est à portée.
if (!isNear(source, { x: 25.7, y: -1347.3, z: 29.5 }, 3)) return fail("TOO_FAR");vehicleOf
Le véhicule dans lequel le joueur est assis.
Côté : serveur · Attend : non
vehicleOf(source: number, options?: { driver?: boolean }): numberProp
Type
Retour l’entité du véhicule, ou 0 à pied (ou hors du siège conducteur avec driver: true).
const vehicle = vehicleOf(source, { driver: true });
if (!vehicle) return fail("NOT_DRIVING");Stockages par joueur
Aucun d’eux n’a d’équivalent Lua : utilisez une table Lua et videz-la dans playerDropped. Créez chaque stockage une seule fois au niveau du module, jamais dans un gestionnaire.
PlayerMap
Une Map indexée par id serveur de joueur, qui se vide d’elle-même quand le joueur se déconnecte. Utilisez-la pour tout ce que vous gardez par joueur (un écran ouvert, un objet sélectionné).
Côté : serveur · Attend : non
new PlayerMap<V>(options?: { onDrop?: (source: number, value: V) => void })Prop
Type
Elle a toutes les méthodes d’une Map JavaScript (get, set, has, delete, size, itération).
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
Des cooldowns par joueur sur l’horloge du jeu, libérés quand le joueur se déconnecte.
Côté : serveur · Attend : non
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éthode | Ce qu’elle fait | Retour |
|---|---|---|
ready(source) | Aucun cooldown ne tourne pour le joueur. | boolean |
start(source, ms) | Démarre (ou redémarre) le cooldown du joueur pour ms millisecondes. | void |
take(source, ms) | Démarre le cooldown quand il est prêt. | true s’il a démarré, false tant qu’un cooldown tourne encore |
left(source) | Millisecondes restantes. | number, 0 quand il est prêt |
clear(source) | Termine le cooldown du joueur. | 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 joueur peut se reconnecter pour réinitialiser une entrée de Cooldowns. Un cooldown qui doit survivre à une reconnexion utilise KeyedCooldowns indexé par online.charId(source).
KeyedCooldowns
Des cooldowns par n’importe quelle clé qui survit à une connexion : un id de personnage, une entité, un id de porte. Les entrées expirées sont nettoyées à mesure que la map grandit.
Côté : serveur · Attend : non
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): voidMêmes méthodes que Cooldowns, par key. start avec un ms de 0 ou moins termine le cooldown.
import { KeyedCooldowns, online } from "@aicore-fivem/lib/server";
import { fail } from "@aicore-fivem/lib/shared";
const robbed = new KeyedCooldowns<string>(); // par id de magasin
const perCharacter = new KeyedCooldowns(); // par id de personnage
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
Une seule action en cours par joueur sur plusieurs callbacks. Le lock: true d’un callback ne couvre que ce callback ; deux actions qui doivent s’exclure (mettre en vente et vendre le même véhicule) partagent un même SourceLock.
Côté : serveur · Attend : non
new SourceLock()
run<T, B>(source: number, busy: B, task: () => Promise<T>): Promise<T | B>
release(source: number): voidProp
Type
Retour (run) le résultat de la tâche, ou busy. release(source) libère le joueur ; appelez-la depuis playerDropped (une tâche encore en cours garde sa propre entrée jusqu’à sa fin).
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
Une limite de rafale par joueur : au plus count actions sur n’importe quelle fenêtre de windowMs. La porte limite déjà la fréquence de chaque entrée ; utilisez ceci pour une limite supplémentaire, plus lente, sur une action précise.
Côté : serveur · Attend : non
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
Retour (take) true en comptant une action, ou false quand le joueur dépasse la limite (non comptée). release(source) oublie le joueur ; appelez-la depuis 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
Les joueurs qui reçoivent les changements en direct d’une clé (les voitures d’un parking, une fiche de personnage ouverte). Une clé par joueur : observer une nouvelle clé arrête l’ancienne. Libéré à la déconnexion ; chaque opération est en temps constant.
Côté : serveur · Attend : non
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éthode | Ce qu’elle fait | Retour |
|---|---|---|
watch(source, key) | Le joueur observe key et arrête d’observer la précédente. | void |
stop(source) | Le joueur arrête d’observer. | void |
of(key) | Les joueurs qui observent key. | un ensemble, ou undefined quand personne n’observe |
keyOf(source) | La clé que le joueur observe. | la clé, 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: [] }; // le 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); // le delta
}Commandes
addCommand : des commandes de chat avec arguments analysés, permissions ACE, suggestions de chat et une ligne dans le panneau d’administration d’ai-core.
Objets
inventory : donner, retirer et compter des objets, coffres, boutiques et objets utilisables, quel que soit l’inventaire que fait tourner le serveur.