Skip to main content

Events

True Life provides type-safe wrappers around FiveM's native events with automatic cleanup via the disposable pattern.

Server Events

Available via @core/events/server:

"use server";

import { fivemEvents } from "@core/events/server";
import { DisposableStore } from "@core/disposable";

const disposables = new DisposableStore();

// Player joining (before fully connected)
disposables.add(
fivemEvents.playerJoining.on((playerId) => {
console.log(`Player ${playerId} joining`);
})
);

// Player dropped (disconnected)
disposables.add(
fivemEvents.playerDropped.on((playerId, reason) => {
console.log(`Player ${playerId} dropped: ${reason}`);
})
);

// Player connecting (with deferrals)
disposables.add(
fivemEvents.playerConnecting.on((playerId, playerName, setKickReason, deferrals) => {
deferrals.defer();
// Async validation...
deferrals.done();
})
);

// Resource lifecycle
disposables.add(
fivemEvents.onResourceStart.on((resourceName) => {
if (resourceName === GetCurrentResourceName()) {
console.log("Our resource started");
}
})
);

disposables.add(
fivemEvents.onResourceStop.on((resourceName) => {
if (resourceName === GetCurrentResourceName()) {
console.log("Our resource stopping");
}
})
);

// Cleanup
disposables.dispose();

Available Server Events

EventParametersDescription
playerJoiningplayerId: numberPlayer beginning to join
playerDroppedplayerId: number, reason: stringPlayer disconnected
playerConnectingplayerId, name, setKickReason, deferralsConnection with deferrals
onResourceStartresourceName: stringResource started
onResourceStopresourceName: stringResource stopping

Client Events

Available via @core/events/client:

"use client";

import { fivemClientEvents } from "@core/events/client";
import { DisposableStore } from "@core/disposable";

const disposables = new DisposableStore();

// Resource lifecycle on client
disposables.add(
fivemClientEvents.onClientResourceStart.on((resourceName) => {
if (resourceName === GetCurrentResourceName()) {
console.log("Our resource started on client");
}
})
);

disposables.add(
fivemClientEvents.onClientResourceStop.on((resourceName) => {
console.log(`Resource ${resourceName} stopped`);
})
);

// Game events
disposables.add(
fivemClientEvents.gameEventTriggered.on((eventName, args) => {
if (eventName === "CEventNetworkEntityDamage") {
const [victim, attacker, ...rest] = args;
console.log(`Entity ${victim} damaged by ${attacker}`);
}
})
);

// Entity lifecycle
disposables.add(
fivemClientEvents.entityCreated.on((entity) => {
console.log(`Entity ${entity} created`);
})
);

disposables.add(
fivemClientEvents.entityRemoved.on((entity) => {
console.log(`Entity ${entity} removed`);
})
);

// Ped changes
disposables.add(
fivemClientEvents.playerPedChanged.on((newPed, oldPed) => {
console.log(`Player ped changed from ${oldPed} to ${newPed}`);
})
);

// Cleanup
disposables.dispose();

Available Client Events

EventParametersDescription
onClientResourceStartresourceName: stringResource started on client
onClientResourceStopresourceName: stringResource stopped on client
gameEventTriggeredname: string, args: unknown[]Game event fired
populationPedCreatingx, y, z, model, overrideCallsPed spawning
playerPedChangednewPed: number, oldPed: numberPlayer ped changed
playerSpawnedspawn: SpawnInfoPlayer spawned
entityCreatedentity: numberEntity created
entityRemovedentity: numberEntity removed

Creating Custom Events

Use createNativeEvent for custom event wrappers:

import { createNativeEvent } from "@core/events/helpers";

// Create a typed event wrapper
const myCustomEvent = createNativeEvent<[data: MyData]>("my:custom:event");

// Subscribe
const disposable = myCustomEvent.on((data) => {
console.log("Received:", data);
});

// Emit (if needed)
myCustomEvent.emit({ value: 123 });

// Cleanup
disposable.dispose();

Event Registration Tracking

For debugging, the framework tracks all registered events:

import { getRegisteredEvents, debugPrintEvents } from "@core/events/tracker";

// Get all registered events
const events = getRegisteredEvents();

// Print debug info
debugPrintEvents();

Disposable Pattern

Events return IDisposable for automatic cleanup:

import { DisposableStore, toDisposable } from "@core/disposable";

export class MyService {
private disposables = new DisposableStore();

@ServerOnly
initServerSide(): void {
// Add event subscriptions
this.disposables.add(
fivemEvents.playerJoining.on(this.handlePlayerJoining)
);

// Add custom cleanup
this.disposables.add(
toDisposable(() => {
clearInterval(this.timer);
})
);
}

@ServerOnly
cleanupServerSide(): void {
// Disposes all registered disposables
this.disposables.dispose();
}

private handlePlayerJoining = (playerId: number) => {
console.log(`Player ${playerId} joining`);
};
}

Best Practices

  1. Always clean up subscriptions - Use DisposableStore
  2. Use typed events - Leverage TypeScript for safety
  3. Check resource name - Prevent handling other resources' events
  4. Handle errors - Wrap handlers in try/catch
  5. Use arrow functions or bind - Preserve this context
  6. Avoid event spam - Debounce frequent events if needed