Interaction points
Points - every place a player uses (counters, shops, doors, NPCs, ATMs, vehicle trunks) as a prompt or a target option, with peds, blips and points built from panel records.
A point is any place a player uses: a shop counter, a door, an NPC, a return spot, every ATM, the trunk of every car. You describe it once (where, what text, what happens) and the lib draws it the way the server owner picked.
Prompt or target
The owner picks the interaction style in ai-core's admin panel (Interaction). The same onPressed runs either way, so your script never cares which one is on.
| Style | What the player sees |
|---|---|
| Prompt | A "press E" box on the right side of the screen while the player stands inside the point's radius. The key is one rebindable keybind in ai-lib (default E), so nothing polls keys every frame. |
| Target | An option in the target eye on the spot, the NPC, the object or the vehicle. It needs one of the target scripts the lib has an adapter for to be running. When several run, the first in this order wins: ai-target, tgiann-radialmenu-target, tgiann-target, sleepless_interact, ox_target, qb-target, interact. |
When the owner picked target but no target script runs, every point is a prompt. A point can override the owner's choice: promptOnly keeps a prompt (things done from a vehicle: fuel pumps, car washes), preferTarget uses a target option whenever a target script runs (seats). When a target script starts or stops, or the owner switches the style, every point moves over by itself.
Only one point action of your resource runs at a time. When onPressed returns a promise, the other points of your resource ignore presses until it settles, so you never need your own busy flag. The player must also pass canShow at the moment of the press.
new Points
Creates a group of points. Make one group per feature at the top of the file and add points to it.
Side: Client · Waits: no
new Points()local points = aiLib.Points.new()Returns
A Points group with the methods below.
Example
import { Points } from "@aicore-fivem/lib/client";
const points = new Points();points.add
Adds a point at a place, or replaces the point with the same id. A point can carry its own NPC (ped) and map blip (blip).
Side: Client · Waits: no
points.add(id: string, options: PointOptions): voidpoints:add(id, options)Parameters
Prop
Type
PointOptions
Prop
Type
PointPed
The ped field takes the LocalPed options except onSpawn and onDespawn (the point uses them):
Prop
Type
PointBlip
The blip field takes the blip options; coords defaults to the point:
Prop
Type
PromptAnchor
What a world function returns (PromptAnchor), one of:
Prop
Type
Returns
void.
Example
import { callAction, createLocale, hasJob, onMembershipChange, Points } from "@aicore-fivem/lib/client";
const Locale = createLocale<typeof import("../../locales/en.json")>();
const points = new Points();
// A shop counter with a shopkeeper and a map blip.
points.add("shop", {
coords: { x: 25.7, y: -1347.3, z: 29.5 },
label: Locale("OPEN_SHOP"),
icon: "basket-shopping",
ped: { model: "mp_m_shopkeep_01", coords: { x: 24.5, y: -1346.6, z: 29.5, heading: 270 } },
blip: { sprite: 52, color: 2, label: Locale("SHOP") },
onPressed: () => callAction("myshop:open", [], { fallback: "SHOP_FAILED" }),
});
// A stash only police can open; the text follows a state.
let locked = true;
points.add("evidence", {
coords: { x: 474.9, y: -996.4, z: 26.3 },
label: () => (locked ? Locale("UNLOCK") : Locale("LOCK")),
icon: "box-archive",
canShow: () => hasJob(["police"], 2),
onPressed: async () => {
const reply = await callAction("myscript:toggle", [], { fallback: "TOGGLE_FAILED" });
if (reply) locked = !locked;
points.refresh("evidence");
},
});
// canShow reads the job: check it again when jobs, duty or gangs change.
onMembershipChange(() => points.refresh());canShow decides only what the player sees. The server must check again (job, distance, stock) in its own handler. A cheater can call your server event without any point.
points.addModel
Adds a point on every world object of some models (ATMs, vending machines, dumpsters, chairs), or replaces the point with the same id. In target mode it is a target option on those models; in prompt mode a prompt shows near the closest one. The scan for the nearest object runs once in ai-lib for every resource, two times a second, and pauses while the player stands still.
Side: Client · Waits: no
points.addModel(id: string, options: ModelPointOptions): voidpoints:addModel(id, options)Parameters
Prop
Type
ModelPointOptions
Prop
Type
Returns
void.
Example
import { callAction, createLocale, Points } from "@aicore-fivem/lib/client";
const Locale = createLocale<typeof import("../../locales/en.json")>();
const points = new Points();
points.addModel("atm", {
models: ["prop_atm_01", "prop_atm_02", "prop_atm_03", "prop_fleeca_atm"],
label: Locale("USE_ATM"),
icon: "credit-card",
onPressed: (atm) => callAction("mybank:atm", [NetworkGetNetworkIdFromEntity(atm)], { fallback: "ATM_FAILED" }),
});Two states of one object (tie and untie a boat) are two model points with opposite canShow, not one label function. A point with more than 6 models finds its objects through an index of the object pool, so hundreds of models cost the same as a few.
points.addVehicle
Adds a point on every networked vehicle (trunk, hood, push, repair), or replaces the point with the same id. In target mode it is the target script's global vehicle option; in prompt mode a prompt shows at the nearest networked vehicle within radius.
Side: Client · Waits: no
points.addVehicle(id: string, options: VehiclePointOptions): voidpoints:addVehicle(id, options)Parameters
Prop
Type
Returns
void.
Example
import { cache, createLocale, Points } from "@aicore-fivem/lib/client";
const Locale = createLocale<typeof import("../../locales/en.json")>();
const points = new Points();
points.addVehicle("trunk", {
label: Locale("OPEN_TRUNK"),
icon: "box-open",
radius: 2.5,
canShow: (vehicle) => vehicle !== 0 && cache.vehicle === false && GetVehicleDoorLockStatus(vehicle) < 2,
onPressed: (vehicle) => SetVehicleDoorOpen(vehicle, 5, false, false),
});points.remove
Removes one point with its prompt, target option, NPC and blip. Works for points made with add, addModel and addVehicle. An unknown id does nothing.
Side: Client · Waits: no
points.remove(id: string): voidpoints:remove(id)Parameters
Prop
Type
Returns
void.
Example
points.remove("shop");points.clear
Removes every point of the group.
Side: Client · Waits: no
points.clear(): voidpoints:clear()Returns
void.
Example
onServerEvent("myscript:jobEnded", () => points.clear());Never rebuild panel points with clear() + add everything on each panel change: with thousands of records one edit would rebuild all of them on every client. Use RecordObjects: an edit rebuilds only its own record.
points.refresh
Reads canShow and function labels again. Call it when something they read has changed: the job, duty, a vehicle, a state the server sent. Prompts update at once; target options read canShow when the eye opens, and a changed label is put on again.
Side: Client · Waits: no
points.refresh(id?: string): voidpoints:refresh() -- every point of the groupParameters
Prop
Type
Returns
void.
Example
import { EntityState, onCache, onMembershipChange, Points, stateBoolean } from "@aicore-fivem/lib/client";
const points = new Points();
const robbed = new EntityState("robbed", stateBoolean);
onCache("vehicle", () => points.refresh());
onMembershipChange(() => points.refresh());
// One register changed: refresh only its point.
robbed.onChange((netId) => points.refresh(`register:${netId}`));Inside a raw AddStateBagChangeHandler the new value is not readable yet: call refresh in a setTimeout(..., 0) there. The lib's state classes (EntityState.onChange) already give you the new value.
points.isInside
Whether the player stands inside a point's radius now (points made with add).
Side: Client · Waits: no
points.isInside(id: string): booleanNo Lua twin.
Parameters
Prop
Type
Returns
boolean: true while the player is inside; false outside, or for an unknown id or a model point.
Example
if (points.isInside("garage")) notify(Locale("PARK_HERE"), "info");points.restyle
Puts every point of the group on the current interaction style. The lib calls it itself when the owner switches the style or a target script starts or stops; you never need it.
Side: Client · Waits: no
points.restyle(): voidNo Lua twin (Lua restyles by itself too).
Returns
void.
Points from panel records
Most points come from a records file of your script's admin panel (shops, garages, rental counters). Mirror the file on the client with AdminWorld, then build each record's points with RecordObjects: an edited record rebuilds only its own points, a deleted one removes them.
new RecordObjects
Keeps the world objects of a records file (points, blips, peds, zones) in step with the file one record at a time. build(key, record) makes one record's objects and returns how to remove them. It builds every record in its constructor.
Side: Client and server (from @aicore-fivem/lib/shared) · Waits: no
new RecordObjects<T>(
source: RecordSource,
file: string,
records: RecordReader<T> | ReadonlyMap<string, T>,
build: (key: string, record: T) => (() => void) | void,
)No Lua twin.
Parameters
Prop
Type
Members
Prop
Type
Example
import { AdminWorld, callAction, createLocale, Points } from "@aicore-fivem/lib/client";
import { readVec3, RecordObjects, str } from "@aicore-fivem/lib/shared";
const Locale = createLocale<typeof import("../../locales/en.json")>();
const world = new AdminWorld(["settings", "shops"]);
const points = new Points();
interface Shop { label: string; coords: { x: number; y: number; z: number } }
const readShop = (_key: string, raw: Record<string, unknown>): Shop | null => {
const coords = readVec3(raw.coords);
return coords ? { label: str(raw.label, "Shop"), coords } : null;
};
const shops = new RecordObjects(world, "shops", readShop, (id, shop) => {
points.add(id, {
coords: shop.coords,
label: Locale("OPEN_SHOP", shop.label),
onPressed: () => callAction("myshop:open", [id], { fallback: "SHOP_FAILED" }),
});
return () => points.remove(id);
});
// A setting every shop reads changed: rebuild them all.
world.onChange("settings", () => shops.rebuildAll());RecordObjects builds in its constructor. Create it after everything build reads, and inside build use the record argument: a class field that holds the RecordObjects is not assigned yet during that first build.
new AdminWorld
The client copy of your script's world panel files (the files whose schema says sync: "all", see Admin panel). It reads the copy FiveM cached at start, asks the server once for the records changed since, then follows live changes. It never polls.
Side: Client · Waits: no (the ready promise tells when the changes since start arrived)
new AdminWorld(files: string[])No Lua twin.
Parameters
Prop
Type
Members
Prop
Type
Example
import { AdminWorld } from "@aicore-fivem/lib/client";
import { num, record } from "@aicore-fivem/lib/shared";
const world = new AdminWorld(["settings"]);
const settings = world.view("settings", (raw) => ({
radius: num(record(raw).radius, 2),
}));
world.onChange("settings", () => console.log("new radius", settings.value.radius));