Server events
Every server-side local event ai-core fires - characters, money, jobs, duty, gangs, metadata, definitions, items, deaths - with arguments and listeners in TypeScript and Lua.
ai-core fires these events on the server with TriggerEvent (local: other server resources hear them, clients never do). The lib's online mirror and the ESX / QB / vRP bridges are built on them, so they are the cheapest way to follow players: no export call, no polling.
Each event's handler runs synchronously while ai-core is in the middle of the change: keep handlers short and never wait inside them. If you need to wait (a database write), start it without blocking.
ai-core:playerLoaded
A player's character was loaded and its data sent to their client. Fires once per character load, also after a character switch. With spawn choice on, it fires before the player picks where to appear; ai-core:playerSpawned follows the pick.
Side: Server · Constant: CORE.playerLoadedEvent
Arguments
Prop
Type
Listener
import { CORE, type CharacterInfo } from "@aicore-fivem/shared";
on(CORE.playerLoadedEvent, (src: number, character: CharacterInfo) => {
console.log(`#${character.id} ${character.firstname} ${character.lastname} loaded (src ${src})`);
});Characters already loaded before your resource started never fire this for you. Use the lib's onCharacter({ loaded }) (aiLib.onCharacter in Lua), which replays them.
ai-core:playerSpawned
The loaded character was put into the world. Fires right after ai-core:playerLoaded, or after the player picked a spawn point when the server lets them choose. Use it for things that need the ped in the world (a welcome message, a teleport).
Side: Server · Constant: CORE.playerSpawnedEvent
Arguments
Prop
Type
Listener
import { notify } from "@aicore-fivem/lib/server";
import { CORE } from "@aicore-fivem/shared";
import { Locale } from "common";
on(CORE.playerSpawnedEvent, (src: number, _charId: number) => {
notify(src, Locale("WELCOME_BACK"), "info");
});ai-core:playerUnloaded
A character is being let go: the player left the server, or went back to the character screen. Fires before ai-core drops the character, so ai-core's exports still answer for it inside your handler. Clean up everything you keep per player here.
Side: Server · Constant: CORE.playerUnloadedEvent
Arguments
Prop
Type
Listener
import { CORE } from "@aicore-fivem/shared";
const activeJobs = new Map<number, string>();
on(CORE.playerUnloadedEvent, (src: number, _charId: number, reason: "dropped" | "switch") => {
activeJobs.delete(src);
console.log(`${src} unloaded (${reason})`);
});With reason = "switch" the same server id may load another character a moment later. Key your per-character data by charId or clear it here; never assume a server id keeps one character.
ai-core:moneyChanged
An account balance of a loaded character changed. Fires after the change is applied, once per change, only when the balance really moved.
Side: Server · Constant: CORE.moneyChangedEvent
Arguments
Prop
Type
Listener
import { CORE } from "@aicore-fivem/shared";
// Flags large cash payments for staff.
on(CORE.moneyChangedEvent, (src: number, account: string, balance: number, delta: number, reason: string) => {
if (account === "cash" && delta >= 100_000)
console.log(`${src} got $${delta} cash (${reason}), now $${balance}`);
});ai-core:jobsChanged
The list of jobs a loaded character holds changed: a job added or removed, or a grade changed.
Side: Server · Constant: CORE.jobsChangedEvent
Arguments
Prop
Type
Listener
import { CORE, type JobMembership } from "@aicore-fivem/shared";
on(CORE.jobsChangedEvent, (src: number, jobs: JobMembership[]) => {
const police = jobs.find((m) => m.job === "police");
console.log(police ? `${src} is police grade ${police.grade}` : `${src} is not police`);
});ai-core:dutyChanged
A loaded character went on duty in a job, or off duty. A player is on duty in at most one job at a time.
Side: Server · Constant: CORE.dutyChangedEvent
Arguments
Prop
Type
Listener
import { CORE } from "@aicore-fivem/shared";
on(CORE.dutyChangedEvent, (src: number, job: string | null) => {
console.log(job ? `${src} on duty: ${job}` : `${src} off duty`);
});To know how many players are on duty in a job, read GlobalState ai:duty:<job> (see State bags) instead of counting these events yourself.
ai-core:gangsChanged
The list of gangs a loaded character belongs to changed: a gang added or removed, or a grade changed.
Side: Server · Constant: CORE.gangsChangedEvent
Arguments
Prop
Type
Listener
import { CORE, type GangMembership } from "@aicore-fivem/shared";
on(CORE.gangsChangedEvent, (src: number, gangs: GangMembership[]) => {
console.log(`${src} is in ${gangs.length} gang(s)`);
});ai-core:activeGangChanged
A loaded character switched the gang they act for, or now acts for none.
Side: Server · Constant: CORE.activeGangChangedEvent
Arguments
Prop
Type
Listener
import { CORE } from "@aicore-fivem/shared";
on(CORE.activeGangChangedEvent, (src: number, gang: string | null) => {
console.log(`${src} now acts for ${gang ?? "no gang"}`);
});ai-core:metaChanged
A metadata value of a loaded character changed. Metadata is a free key / value bag stored with the character; the ESX / QB bridges keep framework metadata (hunger, thirst, stress, isdead...) there.
Side: Server · Constant: CORE.metaChangedEvent
Arguments
Prop
Type
Listener
import { CORE } from "@aicore-fivem/shared";
on(CORE.metaChangedEvent, (src: number, key: string, value: unknown) => {
if (key === "stress" && typeof value === "number" && value >= 90)
console.log(`${src} is very stressed`);
});ai-core:definitionsChanged
A job or a gang was added, changed or deleted in the admin panel. Fires once per job or gang that changed.
Side: Server · Constant: CORE.definitionsChangedEvent
Arguments
Prop
Type
Listener
import { CORE } from "@aicore-fivem/shared";
const labelCache = new Map<string, string>();
// Drop a cached label when the panel changes that job.
on(CORE.definitionsChangedEvent, (file: "jobs" | "gangs", name: string) => {
if (file === "jobs") labelCache.delete(name);
});ai-core:characterScreen
A player's character screen is loading (after joining, or after going back to it). ai-core reads the cards' looks itself; a clothing script listens to draw portraits for the cards.
Side: Server · Constant: CORE.characterScreenEvent
Arguments
Prop
Type
Listener
import { CORE } from "@aicore-fivem/shared";
on(CORE.characterScreenEvent, (src: number, charIds: number[]) => {
console.log(`${src} sees ${charIds.length} character card(s)`);
});ai-core:inventoryChanged
The inventory behind ai-core's inventory layer changed: the picked inventory started or stopped, or the owner picked another one in the panel. Drop anything you cached from the old inventory (item labels, definitions).
Side: Server · Constant: CORE.inventoryChangedEvent
Arguments
Prop
Type
Listener
import { CORE } from "@aicore-fivem/shared";
let itemLabels: Record<string, string> | null = null;
on(CORE.inventoryChangedEvent, (resource: string | null) => {
itemLabels = null; // read again from the new inventory on next use
console.log(`inventory is now ${resource ?? "none"}`);
});ai-core:itemUsed
A player used an item. Carries the needs the item gives (hunger, thirst, stress), so a HUD or needs script applies them without knowing the inventory. ai-hud listens to it.
Side: Server · Constant: CORE.itemUsedEvent
Arguments
Prop
Type
Listener
import { CORE } from "@aicore-fivem/shared";
on(CORE.itemUsedEvent, (src: number, name: string, status: Record<string, number> | null) => {
if (!status) return;
for (const [need, amount] of Object.entries(status))
console.log(`${src} used ${name}: ${need} ${amount > 0 ? "+" : ""}${amount}`);
});Today only ai-inventory reports item uses to ai-core: with another inventory this event does not fire. Listen to this event, not to an inventory's own use event, so your script works when ai-inventory is the inventory and keeps working when others are added.
ai-core:playerDied
A player with a loaded character died. ai-core takes this from FXServer's own ped death report (FXServer 38039 or newer), so no client is trusted.
Side: Server · Constant: CORE.playerDiedEvent
Arguments
Prop
Type
Listener
import { CORE } from "@aicore-fivem/shared";
on(CORE.playerDiedEvent, (src: number, killer: number | null, weaponHash: number) => {
if (killer !== null && killer !== src)
console.log(`${GetPlayerName(String(killer))} killed ${GetPlayerName(String(src))} (weapon ${weaponHash})`);
});This is the game's death, not the death script's "down" state: a death script may keep a downed player alive to animate them. For "is this player down", use the server lib's medical or ai-core's server medical exports.
ai-core:vrp:config
The server owner changed the vRP mapping in ai-core's panel (tab "vRP scripts"). ai-core's vRP bridge listens to it; you only need it if you read the vRP mapping yourself.
Side: Server · Constant: VRP.configEvent
Arguments
Prop
Type
Listener
import { VRP, type VrpConfig } from "@aicore-fivem/shared";
on(VRP.configEvent, (config: VrpConfig) => {
console.log(`${config.permissions.length} vRP permission rule(s)`);
});Events of other ai-* scripts
These are fired by ai-* scripts, not ai-core, but many resources listen to them. Their full reference is on each script's developer page.
ai-vehicles:owners
The owners of owned vehicles in memory changed (a character loaded with their garage, a vehicle bought, sold or deleted). Fires at most once per server tick with every change of that tick. Key scripts use it to keep a plate to owner map without asking ai-vehicles each time.
Side: Server · Constant: VEHICLES_EVENTS.owners
Arguments
Prop
Type
Listener
import { resourceExport } from "@aicore-fivem/lib/server";
import { VEHICLES_EVENTS } from "@aicore-fivem/shared";
const owners = new Map<string, string>();
// Start from the full list, then follow the changes.
for (const [plate, type, id] of resourceExport("ai-vehicles").GetPlateOwners()) owners.set(plate, `${type}:${id}`);
on(VEHICLES_EVENTS.owners, (puts: Array<[string, string, string]>, removes: string[]) => {
for (const [plate, type, id] of puts) owners.set(plate, `${type}:${id}`);
for (const plate of removes) owners.delete(plate);
});chatMessage
ai-chat fires the classic FiveM chatMessage event before it delivers a player's message. Calling CancelEvent() in your handler drops the message.
Side: Server · Constant: none (FiveM's own name)
Arguments
Prop
Type
Listener
on("chatMessage", (source: number, _author: string, text: string) => {
if (text.toLowerCase().includes("discord.gg")) {
CancelEvent();
console.log(`blocked an invite link from ${source}`);
}
});