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