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
| Event | Parameters | Description |
|---|---|---|
playerJoining | playerId: number | Player beginning to join |
playerDropped | playerId: number, reason: string | Player disconnected |
playerConnecting | playerId, name, setKickReason, deferrals | Connection with deferrals |
onResourceStart | resourceName: string | Resource started |
onResourceStop | resourceName: string | Resource 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
| Event | Parameters | Description |
|---|---|---|
onClientResourceStart | resourceName: string | Resource started on client |
onClientResourceStop | resourceName: string | Resource stopped on client |
gameEventTriggered | name: string, args: unknown[] | Game event fired |
populationPedCreating | x, y, z, model, overrideCalls | Ped spawning |
playerPedChanged | newPed: number, oldPed: number | Player ped changed |
playerSpawned | spawn: SpawnInfo | Player spawned |
entityCreated | entity: number | Entity created |
entityRemoved | entity: number | Entity 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
- Always clean up subscriptions - Use
DisposableStore - Use typed events - Leverage TypeScript for safety
- Check resource name - Prevent handling other resources' events
- Handle errors - Wrap handlers in try/catch
- Use arrow functions or bind - Preserve
thiscontext - Avoid event spam - Debounce frequent events if needed