mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-28 17:01:18 +00:00
451 lines
9.7 KiB
Markdown
451 lines
9.7 KiB
Markdown
# ESX Events & Callbacks
|
|
|
|
## Server Callbacks
|
|
|
|
Server callbacks allow the client to request data from the server.
|
|
|
|
### Registering a Server Callback
|
|
|
|
```lua
|
|
-- SERVER
|
|
ESX.RegisterServerCallback('myResource:getPlayerData', function(source, cb, additionalParam)
|
|
local xPlayer = ESX.GetPlayerFromId(source)
|
|
if not xPlayer then return cb(nil) end
|
|
|
|
local data = {
|
|
money = xPlayer.getMoney(),
|
|
job = xPlayer.job.name,
|
|
grade = xPlayer.job.grade,
|
|
param = additionalParam
|
|
}
|
|
|
|
cb(data)
|
|
end)
|
|
```
|
|
|
|
### Calling a Server Callback
|
|
|
|
```lua
|
|
-- CLIENT
|
|
ESX.TriggerServerCallback('myResource:getPlayerData', function(data)
|
|
if data then
|
|
print('Money:', data.money)
|
|
print('Job:', data.job)
|
|
end
|
|
end, 'extraParam')
|
|
```
|
|
|
|
### Common Patterns
|
|
|
|
```lua
|
|
-- Check if player can afford something
|
|
ESX.RegisterServerCallback('shop:canAfford', function(source, cb, itemName)
|
|
local xPlayer = ESX.GetPlayerFromId(source)
|
|
if not xPlayer then return cb(false) end
|
|
|
|
local price = Config.Items[itemName].price
|
|
cb(xPlayer.getMoney() >= price)
|
|
end)
|
|
|
|
-- CLIENT usage
|
|
ESX.TriggerServerCallback('shop:canAfford', function(canAfford)
|
|
if canAfford then
|
|
-- Show buy menu
|
|
else
|
|
lib.notify({title = 'Shop', description = 'Not enough money', type = 'error'})
|
|
end
|
|
end, 'bread')
|
|
```
|
|
|
|
## Client Callbacks
|
|
|
|
**WARNING**: Client callbacks should NEVER be used for sensitive operations! Client can fake any data.
|
|
|
|
### Registering a Client Callback
|
|
|
|
```lua
|
|
-- CLIENT
|
|
ESX.RegisterClientCallback('myResource:getVehicleModel', function(cb, vehicle)
|
|
local model = GetEntityModel(vehicle)
|
|
cb(model)
|
|
end)
|
|
```
|
|
|
|
### Calling a Client Callback (Server)
|
|
|
|
```lua
|
|
-- SERVER
|
|
ESX.TriggerClientCallback(source, 'myResource:getVehicleModel', function(model)
|
|
print('Vehicle model:', model)
|
|
end, vehicleNetId)
|
|
```
|
|
|
|
## ESX Events
|
|
|
|
### Client Events
|
|
|
|
#### esx:playerLoaded
|
|
|
|
Fired when player's character loads.
|
|
|
|
```lua
|
|
-- CLIENT
|
|
AddEventHandler('esx:playerLoaded', function(playerData)
|
|
ESX.PlayerData = playerData
|
|
print('Player loaded:', playerData.firstName, playerData.lastName)
|
|
|
|
-- Initialize your resource
|
|
startClientScripts()
|
|
end)
|
|
```
|
|
|
|
#### esx:updatePlayerData
|
|
|
|
Fired when any PlayerData is updated.
|
|
|
|
```lua
|
|
-- CLIENT
|
|
AddEventHandler('esx:updatePlayerData', function(key, value)
|
|
ESX.PlayerData[key] = value
|
|
|
|
if key == 'job' then
|
|
print('Job changed to:', value.name)
|
|
elseif key == 'money' then
|
|
print('Money updated:', value)
|
|
end
|
|
end)
|
|
```
|
|
|
|
#### esx:setJob
|
|
|
|
Fired when player's job changes.
|
|
|
|
```lua
|
|
-- CLIENT
|
|
AddEventHandler('esx:setJob', function(job)
|
|
ESX.PlayerData.job = job
|
|
print('New job:', job.name, 'Grade:', job.grade)
|
|
|
|
-- Start/stop job-specific systems
|
|
if job.name == 'police' then
|
|
startPoliceBlips()
|
|
else
|
|
stopPoliceBlips()
|
|
end
|
|
end)
|
|
```
|
|
|
|
#### esx:setAccountMoney
|
|
|
|
Fired when player's account money changes.
|
|
|
|
```lua
|
|
-- CLIENT
|
|
AddEventHandler('esx:setAccountMoney', function(account)
|
|
print('Account updated:', account.name, account.money)
|
|
end)
|
|
```
|
|
|
|
#### esx:addInventoryItem
|
|
|
|
Fired when player receives an item.
|
|
|
|
```lua
|
|
-- CLIENT
|
|
AddEventHandler('esx:addInventoryItem', function(item, count)
|
|
print('Received:', count, 'x', item.label)
|
|
end)
|
|
```
|
|
|
|
#### esx:removeInventoryItem
|
|
|
|
Fired when player loses an item.
|
|
|
|
```lua
|
|
-- CLIENT
|
|
AddEventHandler('esx:removeInventoryItem', function(item, count)
|
|
print('Removed:', count, 'x', item.label)
|
|
end)
|
|
```
|
|
|
|
#### esx:onPlayerDeath
|
|
|
|
Fired when player dies.
|
|
|
|
```lua
|
|
-- CLIENT
|
|
AddEventHandler('esx:onPlayerDeath', function(data)
|
|
print('Player died')
|
|
print('Killer:', data.killerServerId)
|
|
|
|
-- Respawn logic
|
|
end)
|
|
```
|
|
|
|
#### esx:onPlayerSpawn
|
|
|
|
Fired when player spawns.
|
|
|
|
```lua
|
|
-- CLIENT
|
|
AddEventHandler('esx:onPlayerSpawn', function()
|
|
print('Player spawned')
|
|
end)
|
|
```
|
|
|
|
#### esx:playerPedChanged
|
|
|
|
Fired when player ped changes (e.g., after model change).
|
|
|
|
```lua
|
|
-- CLIENT
|
|
local playerPed = PlayerPedId()
|
|
|
|
AddEventHandler('esx:playerPedChanged', function(newPed)
|
|
playerPed = newPed
|
|
print('Ped changed:', newPed)
|
|
end)
|
|
```
|
|
|
|
### Server Events
|
|
|
|
#### esx:onPlayerJoined
|
|
|
|
Fired when player connects (before character selection).
|
|
|
|
```lua
|
|
-- SERVER
|
|
AddEventHandler('esx:onPlayerJoined', function()
|
|
local _source = source
|
|
print('Player connected:', _source)
|
|
end)
|
|
```
|
|
|
|
#### esx:playerLoaded
|
|
|
|
Fired when player's character loads.
|
|
|
|
```lua
|
|
-- SERVER
|
|
AddEventHandler('esx:playerLoaded', function(playerId, xPlayer)
|
|
print('Player loaded:', xPlayer.getName())
|
|
|
|
-- Give welcome bonus
|
|
if xPlayer.getMeta('firstTime') == nil then
|
|
xPlayer.addMoney(5000, 'Welcome bonus')
|
|
xPlayer.setMeta('firstTime', false)
|
|
end
|
|
end)
|
|
```
|
|
|
|
#### esx:playerDropped
|
|
|
|
Fired when player disconnects.
|
|
|
|
```lua
|
|
-- SERVER
|
|
AddEventHandler('esx:playerDropped', function(playerId, reason)
|
|
print('Player ' .. playerId .. ' left:', reason)
|
|
end)
|
|
```
|
|
|
|
#### esx:setJob
|
|
|
|
Fired when player's job changes (server-side).
|
|
|
|
```lua
|
|
-- SERVER
|
|
AddEventHandler('esx:setJob', function(playerId, job, lastJob)
|
|
local xPlayer = ESX.GetPlayerFromId(playerId)
|
|
if not xPlayer then return end
|
|
|
|
print(xPlayer.getName() .. ' changed from ' .. lastJob.name .. ' to ' .. job.name)
|
|
|
|
-- Log job change
|
|
MySQL.Async.execute('INSERT INTO job_changes (identifier, old_job, new_job) VALUES (@identifier, @old, @new)', {
|
|
['@identifier'] = xPlayer.identifier,
|
|
['@old'] = lastJob.name,
|
|
['@new'] = job.name
|
|
})
|
|
end)
|
|
```
|
|
|
|
## Secure Net Events
|
|
|
|
Use SecureNetEvent for client events that should only be triggered by server.
|
|
|
|
### Registering Secure Net Event
|
|
|
|
```lua
|
|
-- CLIENT
|
|
ESX.SecureNetEvent('myResource:giveReward', function(amount, reason)
|
|
-- Only server can trigger this
|
|
print('Received reward:', amount, reason)
|
|
|
|
lib.notify({
|
|
title = 'Reward',
|
|
description = 'You received $' .. amount .. ' for ' .. reason,
|
|
type = 'success'
|
|
})
|
|
end)
|
|
```
|
|
|
|
### Triggering Secure Net Event
|
|
|
|
```lua
|
|
-- SERVER
|
|
local xPlayer = ESX.GetPlayerFromId(source)
|
|
xPlayer.triggerEvent('myResource:giveReward', 500, 'completing mission')
|
|
```
|
|
|
|
## Custom Events
|
|
|
|
### Triggering Client Event from Server
|
|
|
|
```lua
|
|
-- SERVER
|
|
local xPlayer = ESX.GetPlayerFromId(source)
|
|
xPlayer.triggerEvent('myResource:openMenu', menuData)
|
|
|
|
-- Or using TriggerClientEvent
|
|
TriggerClientEvent('myResource:openMenu', source, menuData)
|
|
|
|
-- Or for multiple players
|
|
local officers = ESX.GetExtendedPlayers('job', 'police')
|
|
for i, xPlayer in ipairs(officers) do
|
|
xPlayer.triggerEvent('myResource:alert', 'Code 3 at Legion Square')
|
|
end
|
|
```
|
|
|
|
### Triggering Server Event from Client
|
|
|
|
```lua
|
|
-- CLIENT
|
|
TriggerServerEvent('myResource:buyItem', 'bread')
|
|
```
|
|
|
|
### Receiving Custom Events
|
|
|
|
```lua
|
|
-- CLIENT
|
|
RegisterNetEvent('myResource:openMenu')
|
|
AddEventHandler('myResource:openMenu', function(menuData)
|
|
-- Open menu with data
|
|
end)
|
|
|
|
-- SERVER
|
|
RegisterNetEvent('myResource:buyItem')
|
|
AddEventHandler('myResource:buyItem', function(itemName)
|
|
local xPlayer = ESX.GetPlayerFromId(source)
|
|
if not xPlayer then return end
|
|
|
|
-- Validate and process purchase
|
|
local price = Config.Items[itemName].price
|
|
if xPlayer.getMoney() >= price then
|
|
xPlayer.removeMoney(price, 'Bought ' .. itemName)
|
|
xPlayer.addInventoryItem(itemName, 1)
|
|
end
|
|
end)
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Always validate server-side**:
|
|
```lua
|
|
-- BAD: Trust client data
|
|
RegisterNetEvent('shop:buy')
|
|
AddEventHandler('shop:buy', function(price)
|
|
local xPlayer = ESX.GetPlayerFromId(source)
|
|
xPlayer.removeMoney(price) -- Client controls price!
|
|
end)
|
|
|
|
-- GOOD: Server validates
|
|
RegisterNetEvent('shop:buy')
|
|
AddEventHandler('shop:buy', function(itemName)
|
|
local xPlayer = ESX.GetPlayerFromId(source)
|
|
if not xPlayer then return end
|
|
|
|
local price = Config.Items[itemName].price
|
|
if xPlayer.getMoney() >= price then
|
|
xPlayer.removeMoney(price, 'Bought ' .. itemName)
|
|
xPlayer.addInventoryItem(itemName, 1)
|
|
end
|
|
end)
|
|
```
|
|
|
|
2. **Use callbacks for data requests**:
|
|
```lua
|
|
-- GOOD: Use callback
|
|
ESX.TriggerServerCallback('shop:canAfford', function(canAfford)
|
|
if canAfford then
|
|
-- Do something
|
|
end
|
|
end, 'bread')
|
|
|
|
-- BAD: Use event
|
|
TriggerServerEvent('shop:checkAfford', 'bread')
|
|
RegisterNetEvent('shop:affordResult')
|
|
AddEventHandler('shop:affordResult', function(canAfford)
|
|
-- Client can fake this event
|
|
end)
|
|
```
|
|
|
|
3. **Use SecureNetEvent for important client events**:
|
|
```lua
|
|
-- CLIENT
|
|
ESX.SecureNetEvent('police:giveArmor', function()
|
|
SetPedArmour(PlayerPedId(), 100)
|
|
end)
|
|
|
|
-- SERVER (validated)
|
|
local xPlayer = ESX.GetPlayerFromId(source)
|
|
if xPlayer.job.name == 'police' then
|
|
xPlayer.triggerEvent('police:giveArmor')
|
|
end
|
|
```
|
|
|
|
4. **Always check for nil**:
|
|
```lua
|
|
RegisterNetEvent('myResource:action')
|
|
AddEventHandler('myResource:action', function()
|
|
local xPlayer = ESX.GetPlayerFromId(source)
|
|
if not xPlayer then return end
|
|
|
|
-- Safe to use xPlayer
|
|
end)
|
|
```
|
|
|
|
5. **Use proper event naming**:
|
|
```lua
|
|
-- GOOD: Descriptive names
|
|
'myResource:openShopMenu'
|
|
'myResource:buyItem'
|
|
'myResource:sellItem'
|
|
|
|
-- BAD: Vague names
|
|
'myResource:event1'
|
|
'myResource:action'
|
|
'openMenu'
|
|
```
|
|
|
|
6. **Listen to ESX events for state changes**:
|
|
```lua
|
|
-- CLIENT: React to job changes
|
|
AddEventHandler('esx:setJob', function(job)
|
|
if job.name == 'police' then
|
|
startPoliceFeatures()
|
|
else
|
|
stopPoliceFeatures()
|
|
end
|
|
end)
|
|
|
|
-- Check on resource start too
|
|
CreateThread(function()
|
|
while not ESX.IsPlayerLoaded() do Wait(100) end
|
|
|
|
if ESX.PlayerData.job.name == 'police' then
|
|
startPoliceFeatures()
|
|
end
|
|
end)
|
|
```
|