Camera and timing
ScriptCamera, TickLoop, AreaFollower and ServerClock - scripted cameras, per-frame jobs that run only while needed, reporting the player's area to the server, and the server's clock on the client.
A scripted camera for showrooms and scenes, a per-frame job you switch on only while it is needed, the client half of the server's area streaming, and the server's clock for countdowns. Periodic work in general is every(ms, fn) (see Lib: shared and fivem); never setInterval or a bare setTick.
ScriptCamera
new ScriptCamera
A scripted camera: a shop's showroom view, an editor, a scene, binoculars. start creates and shows it, the other methods move and change it, stop eases back to the game camera and destroys it. A camera still running when your resource stops is given back at once, so the player is never stuck in it.
Side: Client · Waits: no
new ScriptCamera(type?: string)No Lua twin: Lua resources use CreateCam, RenderScriptCams and DestroyCam.
Parameters
Prop
Type
CameraView
Where a camera looks, used by start, move and glideTo:
Prop
Type
Members
Prop
Type
Example
import { ScriptCamera } from "@aicore-fivem/lib/client";
const camera = new ScriptCamera();
const CAR = { x: -44.4, y: -1097.1, z: 26.4 };
export function openShowroom(): void {
camera.start({ x: -40.2, y: -1100.5, z: 27.6 }, { pointAt: CAR, fov: 50 }, 800);
}
export function frontView(): void {
camera.glideTo({ x: -47.5, y: -1093.8, z: 27.2 }, { pointAt: CAR, fov: 45 }, 1200);
}
export function closeShowroom(): void {
camera.stop(800);
}camera.start
Creates the camera (when it does not exist yet), puts it at a point and makes it the view, easing in from the game camera.
Side: Client · Waits: no
camera.start(at: Vec3, view?: CameraView, easeMs?: number): voidParameters
Prop
Type
Returns
void. See the class example.
camera.move
Moves the camera, at once or gliding there (the game interpolates). Does nothing while stopped.
Side: Client · Waits: no
camera.move(at: Vec3, view?: CameraView, ms?: number): voidParameters
Prop
Type
Returns
void.
Example
camera.move({ x: -42.0, y: -1099.0, z: 28.5 }, { pointAt: CAR }, 600);camera.glideTo
Blends to a new shot: a second camera at the new place takes over from this one, and the old one is destroyed after the blend. Shake and blur carry over. Starts the camera when it is stopped (or when ms is 0).
Side: Client · Waits: no
camera.glideTo(at: Vec3, view: CameraView, ms: number): voidParameters
Prop
Type
Returns
void. See the class example.
camera.attach
Makes the camera follow an entity at an offset in the entity's own space.
Side: Client · Waits: no
camera.attach(entity: number, offset: Vec3, relative?: boolean): voidParameters
Prop
Type
Returns
void.
Example
import { cache, ScriptCamera } from "@aicore-fivem/lib/client";
const chase = new ScriptCamera();
if (cache.vehicle !== false) {
chase.start({ x: 0, y: 0, z: 0 });
chase.attach(cache.vehicle, { x: 0, y: -6, z: 2 });
}camera.pointAt
Makes the camera keep looking at a point.
Side: Client · Waits: no
camera.pointAt(target: Vec3): voidParameters
Prop
Type
Returns
void.
camera.setFov
Changes the field of view (zoom).
Side: Client · Waits: no
camera.setFov(fov: number): voidParameters
Prop
Type
Returns
void.
Example
// Binoculars: zoom with the mouse wheel.
let fov = 40;
export function zoom(step: number): void {
fov = Math.min(70, Math.max(5, fov + step));
camera.setFov(fov);
}camera.shake
Adds the game's camera shake. Amplitude 0 stops it.
Side: Client · Waits: no
camera.shake(type: string, amplitude: number): voidParameters
Prop
Type
Returns
void.
Example
camera.shake("HAND_SHAKE", 0.3);camera.blur
Depth of field: sharp between two distances, blurred outside. Pass null for none. It needs a per-frame native, which runs only while a blur is set.
Side: Client · Waits: no
camera.blur(dof: CameraBlur | null): voidParameters
Prop
Type
Returns
void.
Example
camera.blur({ near: 2, far: 8, strength: 0.8 });camera.stop
Gives the view back to the game camera (easing over easeMs) and destroys the camera. Shake and blur end too. Does nothing while stopped.
Side: Client · Waits: no
camera.stop(easeMs?: number): voidParameters
Prop
Type
Returns
void. See the class example.
TickLoop
new TickLoop
A per-frame job (every(0, fn) on your resource's one scheduler) that is off until you switch it on. Use it for anything that must run every frame only sometimes: drawing a marker while near, a placement preview, disabled firing while aiming at a friend.
Side: Client · Waits: no
new TickLoop(onFrame: () => void, name?: string)No Lua twin: aiLib.every(0, fn, { active = false, name = name }) and job:set(active).
Parameters
Prop
Type
Members
Prop
Type
Example
import { TickLoop, Zones } from "@aicore-fivem/lib/client";
const SPOT = { x: 215.8, y: -810.1, z: 30.7 };
const light = new TickLoop(() => {
DrawLightWithRange(SPOT.x, SPOT.y, SPOT.z + 2, 61, 247, 158, 8, 4);
}, "garageLight");
const zones = new Zones();
zones.add("garage", {
shape: { type: "sphere", center: SPOT, radius: 25 },
onEnter: () => light.set(true),
onExit: () => light.set(false),
});Every job of your resource runs from one setTick that exists only while a job is on. Create the loop once at the top of the file and switch it, instead of creating a new one per start.
AreaFollower
new AreaFollower
The client half of a server AreaStream (Area streaming): it tells the server which grid cell the player stands in whenever it changes (checked every 2 seconds while started), so the server sends only the world objects (drops, plants, placed props) around the player. Create one at the top of the file.
Side: Client · Waits: no
new AreaFollower(event: string, cellSize?: number)No Lua twin.
Parameters
Prop
Type
Members
Prop
Type
Example
import { AreaFollower, onServerEvent } from "@aicore-fivem/lib/client";
const follower = new AreaFollower("myplants:cell");
follower.start();
const plants = new Map<number, unknown>();
// The stream sends (reset, puts, removes): one snapshot with reset true, then only changes.
onServerEvent(
"myplants:area",
{
args: ([reset, puts, removes]) =>
typeof reset === "boolean" && Array.isArray(puts) && Array.isArray(removes)
? ([reset, puts, removes] as const)
: null,
},
(reset, puts, removes) => {
if (reset) plants.clear();
for (const plant of puts as Array<{ id?: unknown } | null>)
if (typeof plant?.id === "number") plants.set(plant.id, plant);
for (const id of removes) if (typeof id === "number") plants.delete(id);
},
);The server checks a reported cell against the player's real position (followReported accepts only cells within one cell of the ped), so a cheater cannot ask for a far area.
ServerClock
new ServerClock
The server's clock on a client. Client and server clocks can differ, so a countdown to a server time (a robbery cooldown, an auction end) must be turned into local time first. Call sync with the server's Date.now() whenever a snapshot or start message arrives.
Side: Client (from @aicore-fivem/lib/shared) · Waits: no
new ServerClock()No Lua twin.
Members
Prop
Type
Example
import { every, onServerEvent } from "@aicore-fivem/lib/client";
import { ServerClock } from "@aicore-fivem/lib/shared";
const clock = new ServerClock();
let endsAt = 0; // local time
onServerEvent(
"myauction:state",
{ args: ([now, ends]) => (typeof now === "number" && typeof ends === "number" ? [now, ends] : null) },
(now, ends) => {
clock.sync(now);
endsAt = clock.toLocal(ends);
},
);
every(1000, () => {
const left = Math.max(0, Math.round((endsAt - Date.now()) / 1000));
SendNUIMessage({ action: "countdown", data: left });
});The server sends the end time once; the client counts down on its own clock. Never send the remaining time every second.