AI Core

Scheduling and lifecycle

every, onStop, caller, ByResource, Reasons, sleep and waitFor: periodic work, waiting, and data that belongs to the resource that registered it.

TypeScript
import { every, onStop, caller, ByResource, Reasons } from "@aicore-fivem/lib/fivem";
import { sleep, waitFor } from "@aicore-fivem/lib/shared";

Periodic work

every

Runs a function every ms milliseconds (0 = every frame) from your resource's single scheduler. Use it instead of setInterval, setTick or a CreateThread loop with Wait: all jobs of a resource share one tick, and that tick exists only while at least one job is switched on, so an idle resource costs nothing in resmon.

Side: Client and server · Waits: no

Signature
every(ms: number, fn: () => unknown, options?: EveryOptions): Job
Lua
local job = aiLib.every(ms, fn, options) -- job:set(active), job:stop(), job.running

Parameters

Prop

Type

EveryOptions

Prop

Type

Returns

Job: a handle to switch the job on and off.

Prop

Type

Example

A per-frame job that runs only while it has something to do:

import { every, cache, onCache } from "@aicore-fivem/lib/client";

// Hides the crosshair every frame, but only while a weapon is in hand.
const hideCrosshair = every(0, () => HideHudComponentThisFrame(14), {
  active: cache.weapon !== false,
  name: "hide crosshair",
});

onCache("weapon", (weapon) => hideCrosshair.set(weapon !== false));

A job must never wait: no Wait in Lua, no long await chain that you expect to finish before the next run in TypeScript. In Lua a Wait inside a job holds every other job of the resource; start a thread from the job for that. A failing job is logged at most once every 10 seconds (the errors in between are counted into the next line).

Waiting

sleep

Waits ms milliseconds.

Side: Client, server and NUI · Waits: yes (Promise)

Signature
sleep(ms: number): Promise<void>

Lua: no twin; use Wait(ms) inside a thread.

Parameters

Prop

Type

Returns

Promise<void> that resolves after ms milliseconds.

Example

import { sleep } from "@aicore-fivem/lib/shared";
import { fadeOut, fadeIn } from "@aicore-fivem/lib/client";

async function blink(): Promise<void> {
  await fadeOut(300);
  await sleep(1000);
  await fadeIn(300);
}

waitFor

Checks a condition until it gives a value, then returns that value. Use it to wait for something the game does on its own time: an entity to exist, a model to load, a value to arrive.

Side: Client, server and NUI · Waits: yes (Promise in TypeScript, yields the thread in Lua)

Signature
waitFor<T>(check: () => T | undefined, timeoutMs?: number | false, intervalMs?: number): Promise<T>
Lua
local value = aiLib.waitFor(check, timeout, interval) -- value, or nil after the timeout; call inside a thread

Parameters

Prop

Type

Returns

TypeScript: Promise<T> that resolves with the first value check returned that was not undefined. It rejects with Error("waitFor timed out after <ms> ms") after the timeout.

Lua: the value, or nil after the timeout (it does not throw).

Example

import { waitFor } from "@aicore-fivem/lib/shared";

async function vehicleFromNet(netId: number): Promise<number | null> {
  try {
    return await waitFor(() => {
      const vehicle = NetworkGetEntityFromNetworkId(netId);
      return DoesEntityExist(vehicle) ? vehicle : undefined;
    }, 5000);
  } catch {
    return null; // not streamed in within 5 seconds
  }
}

In TypeScript, always handle the rejection (try / catch or .catch()), or pass false as the timeout. In Lua, waitFor only works inside a thread.

Resource lifecycle

onStop

Runs a function when your resource stops (on the side the code runs on). Use it to delete what natives created and nothing else removes: entities you spawned, cameras, a NUI focus.

Side: Client and server · Waits: no

Signature
onStop(fn: () => void): void

Lua: no twin; use AddEventHandler('onResourceStop', function(name) if name == GetCurrentResourceName() then ... end end).

Parameters

Prop

Type

Returns

void.

Example

resource/server/index.ts
import { onStop } from "@aicore-fivem/lib/server";

const spawned = new Set<number>();

onStop(() => {
  for (const entity of spawned) if (DoesEntityExist(entity)) DeleteEntity(entity);
});

All onStop, ByResource and Reasons of a resource share one stop listener, so adding many costs nothing extra.

caller

The name of the resource that called the export now running. Inside your own code (not in an export call) it is your resource's name.

Side: Client and server · Waits: no

Signature
caller(): string

Lua: no twin; use GetInvokingResource() or GetCurrentResourceName().

Returns

string: the calling resource's name, or this resource's name when no other resource is calling.

Example

resource/server/index.ts
import { registerExport, caller } from "@aicore-fivem/lib/server";

registerExport("Ping", () => {
  console.log(`${caller()} asked for a ping`);
  return true;
});

ByResource

A map whose entries belong to the resource that set them. When that resource stops, its entries are removed at once (and onDrop runs for each). Use it for things other resources register through your exports: hooks, prompts, items, menu entries.

Side: Client and server · Waits: no

Signature
new ByResource<K, V>(options?: { onDrop?: (resource: string, key: K, value: V) => void })

Lua: no twin.

Parameters

Prop

Type

Methods

Prop

Type

Example

resource/server/index.ts
import { ByResource, registerExport } from "@aicore-fivem/lib/server";
import { readText } from "@aicore-fivem/lib/shared";

type Hook = (source: number, text: string) => boolean;
const hooks = new ByResource<string, Hook>({
  onDrop: (resource, id) => console.log(`${resource} stopped: hook ${id} removed`),
});

// Another resource: exports["my-chat"].AddHook("swear-filter", (source, text) => ...)
registerExport("AddHook", (id: unknown, hook: unknown) => {
  const name = readText(id, 64);
  if (name === null || typeof hook !== "function") return false;
  hooks.set(name, hook as Hook); // owner = the resource calling AddHook
  return true;
});

export function allowed(source: number, text: string): boolean {
  for (const hook of hooks.values()) if (!hook(source, text)) return false;
  return true;
}

Reasons

A switch that is on while at least one reason is set. Each resource sets its own named reasons, and a stopping resource's reasons are cleared. Use it for "hide the HUD", "pause the sync", "block the menu": several scripts can ask at once, and the state only flips back when the last one lets go.

Side: Client and server · Waits: no

Signature
new Reasons()

Lua: no twin.

Methods

Prop

Type

Example

resource/client/index.ts
import { Reasons, registerExport } from "@aicore-fivem/lib/client";
import { readText } from "@aicore-fivem/lib/shared";

const hidden = new Reasons();
hidden.onChange((any) => SendNUIMessage({ action: "visible", value: !any }));

// Other resources: exports["my-hud"].Hide("cutscene") and exports["my-hud"].Show("cutscene")
registerExport("Hide", (reason: unknown) => {
  const name = readText(reason, 64);
  if (name !== null) hidden.set(name, true);
});
registerExport("Show", (reason: unknown) => {
  const name = readText(reason, 64);
  if (name !== null) hidden.set(name, false);
});

On this page