Client events are essential for handling player-specific actions, UI updates, and local game state changes in RSG Framework. They run on each player’s machine and allow for responsive, individualized gameplay experiences.
Client events run locally on each player’s machine and can be triggered by the server using TriggerClientEvent or by other client-side code using TriggerEvent.
Triggered when a player successfully loads into the server after character selection. This is the most important event for initializing player-specific client-side features.
Copy
-- CLIENT SIDElocal RSGCore = exports['rsg-core']:GetCoreObject()RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', function() local PlayerData = RSGCore.Functions.GetPlayerData() print('Player loaded:', PlayerData.charinfo.firstname) -- Initialize player-specific features -- Set up HUD -- Load player blips -- Start player threadsend)
Use Cases:
Initialize HUD elements
Start player status monitors
Load player-specific blips/markers
Set up keybinds
Initialize job-specific UI
Restore player settings
Complete Example:
Copy
-- CLIENT SIDERegisterNetEvent('RSGCore:Client:OnPlayerLoaded', function() local PlayerData = RSGCore.Functions.GetPlayerData() -- Start HUD TriggerEvent('hud:client:LoadHud', PlayerData) -- Start status update thread CreateThread(function() while true do -- Update hunger/thirst display Wait(30000) -- Every 30 seconds end end) -- Show welcome message lib.notify({ title = 'Welcome Back!', description = 'Hello, ' .. PlayerData.charinfo.firstname, type = 'success', duration = 5000 }) -- Job-specific initialization if PlayerData.job.name == 'police' then TriggerEvent('police:client:initPoliceJob') endend)
Triggered when a player logs out or returns to character selection. Use this to clean up client-side data and stop threads.
Copy
-- CLIENT SIDERegisterNetEvent('RSGCore:Client:OnPlayerUnload', function() print('Player unloading...') -- Clean up HUD TriggerEvent('hud:client:UnloadHud') -- Stop custom threads -- Clear temporary blips -- Reset UI elements -- Save client settingsend)
Use Cases:
Stop running threads
Clear temporary blips/markers
Reset UI to default state
Save client-side settings
Clean up job-specific features
Complete Example:
Copy
-- CLIENT SIDElocal activeThreads = {}local playerBlips = {}RegisterNetEvent('RSGCore:Client:OnPlayerUnload', function() -- Stop all active threads for _, thread in pairs(activeThreads) do thread.active = false end activeThreads = {} -- Remove all player blips for _, blip in pairs(playerBlips) do if DoesBlipExist(blip) then RemoveBlip(blip) end end playerBlips = {} -- Clear UI SendNUIMessage({ action = 'hideAll' }) print('Player unloaded and cleaned up')end)
Triggered whenever player data changes (job update, money change, metadata update, etc.). Essential for keeping UI synchronized with server data.
Copy
-- CLIENT SIDElocal RSGCore = exports['rsg-core']:GetCoreObject()RegisterNetEvent('RSGCore:Player:SetPlayerData', function(newData) -- Update local PlayerData RSGCore.PlayerData = newData -- Update HUD with new data TriggerEvent('hud:client:UpdatePlayerData', newData) print('Player data updated')end)
Use Cases:
Update HUD when money changes
Refresh job display when job updates
Update status bars when metadata changes
Sync inventory UI
Update player name displays
Complete Example with Specific Updates:
Copy
-- CLIENT SIDElocal RSGCore = exports['rsg-core']:GetCoreObject()local lastMoney = {}local lastJob = nilRegisterNetEvent('RSGCore:Player:SetPlayerData', function(newData) local oldData = RSGCore.PlayerData RSGCore.PlayerData = newData -- Check for money changes if oldData.money then for moneyType, amount in pairs(newData.money) do if lastMoney[moneyType] and lastMoney[moneyType] ~= amount then local difference = amount - lastMoney[moneyType] if difference > 0 then lib.notify({ description = '+$' .. difference .. ' ' .. moneyType, type = 'success' }) end end lastMoney[moneyType] = amount end end -- Check for job changes if newData.job and newData.job.name ~= lastJob then lib.notify({ title = 'Job Updated', description = 'You are now: ' .. newData.job.label, type = 'info' }) lastJob = newData.job.name -- Trigger job-specific initialization TriggerEvent('job:client:onJobChange', newData.job) end -- Update HUD SendNUIMessage({ action = 'updatePlayerData', data = newData })end)
-- CLIENT SIDElocal currentJobBlips = {}RegisterNetEvent('RSGCore:Client:OnJobUpdate', function(job) -- Clean up old job blips for _, blip in pairs(currentJobBlips) do if DoesBlipExist(blip) then RemoveBlip(blip) end end currentJobBlips = {} -- Add new job blips if job.name == 'police' then -- Add police station blip local blip = Citizen.InvokeNative(0x554D9D53F696D002, 1664425300, Config.PoliceStation.x, Config.PoliceStation.y, Config.PoliceStation.z) SetBlipSprite(blip, -1282792512, 1) currentJobBlips[#currentJobBlips + 1] = blip end lib.notify({ title = 'Job Changed', description = 'You are now a ' .. job.label, type = 'info' })end)
-- CLIENT SIDERegisterNetEvent('RSGCore:Command:SpawnVehicle', function(model) local ped = PlayerPedId() local coords = GetEntityCoords(ped) local heading = GetEntityHeading(ped) -- Load vehicle model local modelHash = GetHashKey(model) lib.requestModel(modelHash) -- Spawn vehicle local vehicle = CreateVehicle(modelHash, coords.x + 2, coords.y, coords.z, heading, true, false) -- Set player in vehicle SetPedIntoVehicle(ped, vehicle, -1) -- Clean up SetModelAsNoLongerNeeded(modelHash) lib.notify({ description = 'Vehicle spawned: ' .. model, type = 'success' })end)
Trigger from Server:
Copy
-- SERVER SIDERSGCore.Commands.Add('spawnveh', 'Spawn vehicle', {{name = 'model', help = 'Vehicle model'}}, true, function(source, args) TriggerClientEvent('RSGCore:Command:SpawnVehicle', source, args[1])end, 'admin')
Trigger from Client:
Copy
-- CLIENT SIDERegisterCommand('spawnveh', function(_, args) local model = args[1] if not model then lib.notify({description = 'Usage: /spawnveh [model]', type = 'error'}) return end TriggerEvent('RSGCore:Command:SpawnVehicle', model)end)
-- CLIENT SIDERegisterNetEvent('RSGCore:Command:DeleteVehicle', function() local ped = PlayerPedId() local vehicle = GetVehiclePedIsIn(ped, false) -- If not in vehicle, get closest vehicle if vehicle == 0 then local coords = GetEntityCoords(ped) vehicle = GetClosestVehicle(coords.x, coords.y, coords.z, 5.0, 0, 70) end if vehicle ~= 0 then DeleteEntity(vehicle) lib.notify({ description = 'Vehicle deleted', type = 'success' }) else lib.notify({ description = 'No vehicle nearby', type = 'error' }) endend)
-- CLIENT SIDERegisterNetEvent('myresource:client:notify', function(message, type) lib.notify({ description = message, type = type or 'info' })end)-- Trigger from server-- SERVER SIDETriggerClientEvent('myresource:client:notify', source, 'Action completed!', 'success')
-- GOODRegisterNetEvent('myEvent', function() local PlayerData = RSGCore.Functions.GetPlayerData() -- Use fresh dataend)-- BAD (can be outdated)local PlayerData = RSGCore.Functions.GetPlayerData()RegisterNetEvent('myEvent', function() -- Using old PlayerDataend)
-- GOODlocal myBlips = {}local myThreads = {}RegisterNetEvent('RSGCore:Client:OnPlayerUnload', function() -- Clean up blips for _, blip in pairs(myBlips) do if DoesBlipExist(blip) then RemoveBlip(blip) end end -- Stop threads for _, thread in pairs(myThreads) do thread.active = false endend)
-- GOODRegisterNetEvent('myEvent', function(data) if not data or type(data) ~= 'table' then print('Invalid data received') return end -- Process dataend)
CreateThread(function() local shopCoords = vector3(2633.0, -1220.0, 53.0) local inRange = false while true do local ped = PlayerPedId() local coords = GetEntityCoords(ped) local distance = #(coords - shopCoords) if distance < 2.0 and not inRange then inRange = true lib.showTextUI('[E] Open Shop') elseif distance >= 2.0 and inRange then inRange = false lib.hideTextUI() end if inRange and IsControlJustPressed(0, RSGCore.Shared.Keybinds['E']) then TriggerServerEvent('shop:server:openShop') end Wait(inRange and 0 or 1000) endend)
RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', function() CreateThread(function() while true do local PlayerData = RSGCore.Functions.GetPlayerData() if PlayerData.metadata then -- Update HUD SendNUIMessage({ action = 'updateStatus', hunger = PlayerData.metadata.hunger, thirst = PlayerData.metadata.thirst }) end Wait(5000) -- Update every 5 seconds end end)end)
-- Wait for player to loadRegisterNetEvent('myEvent', function() local PlayerData = RSGCore.Functions.GetPlayerData() if not PlayerData or not PlayerData.citizenid then print('Player not loaded yet') return end -- Safe to use PlayerDataend)
-- Make sure to listen to PlayerData updatesRegisterNetEvent('RSGCore:Player:SetPlayerData', function(newData) -- Update UI SendNUIMessage({ action = 'update', data = newData })end)