Parent : [[INDEX-FIVEM-GAMEPLAY]]
FiveM - Dynamic Document Generation : ID, permis, requête SQL joueur, envoi proximité
Problème
Les documents (carte d'identité, permis) doivent être générés dynamiquement avec les vraies données du joueur, affichés en UI NUI, et transmis aux joueurs proches pour inspection.
Solution
Génération dynamique de documents avec requête SQL des données joueur, HTML span coloré, envoi aux joueurs à proximité via boucle GetPlayers() avec check distance.
-- server.lua
function showDocument(source, docType, imageUrl, requester)
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer then return end
-- Récupération données joueur
local result = MySQL.query.await(
'SELECT firstname, lastname, sex, dateofbirth, height FROM users WHERE identifier = ?',
{ xPlayer.getIdentifier() }
)
if not result or #result == 0 then return end
local playerInfo = {
firstname = result[1].firstname,
lastname = result[1].lastname,
sex = result[1].sex,
dateofbirth = result[1].dateofbirth,
height = result[1].height
}
-- Vérification document valide (si permis)
if docType ~= 'id' then
local hasLicense = MySQL.scalar.await(
'SELECT 1 FROM user_licenses WHERE owner = ? AND type = ?',
{ xPlayer.getIdentifier(), docType }
)
if not hasLicense then
TriggerClientEvent('ox_lib:notify', source, { type = 'error', description = 'Permis non détenu' })
return
end
end
-- Envoi aux joueurs proches
local playerCoords = GetEntityCoords(GetPlayerPed(source))
local playerPed = GetPlayerPed(source)
for _, targetId in ipairs(ESX.GetPlayers()) do
local targetPed = GetPlayerPed(targetId)
if targetPed ~= playerPed then
local targetCoords = GetEntityCoords(targetPed)
local dist = #(playerCoords - targetCoords)
if dist < 3.0 then
TriggerClientEvent('vms_document:client:send', targetId, docType, playerInfo, imageUrl)
end
end
end
-- Envoi à l'émetteur aussi
TriggerClientEvent('vms_document:client:send', source, docType, playerInfo, imageUrl)
end
-- client.lua
RegisterNetEvent('vms_document:client:send')
AddEventHandler('vms_document:client:send', function(docType, playerInfo, imageUrl)
-- Activation NUI
SetNuiFocus(true, true)
SendNUIMessage({
action = 'openDocument',
type = docType,
data = playerInfo,
image = imageUrl
})
end)
-- Fermeture NUI
RegisterNUICallback('close', function(data, cb)
SetNuiFocus(false, false)
cb('ok')
end)
Pourquoi
- Requête SQL garantit les données réelles (pas de spoof client)
- Envoi à proximité simule le monde physique (on montre son ID à quelqu'un proche)
- NUI focus automatique pour l'interaction
Quand l'utiliser
- Cartes d'identité, permis de conduire, licences d'armes
- Documents RP (fiches médicales, casiers judiciaires)
Quand NE PAS l'utiliser
- Informations toujours visibles (HUD) → pas besoin de document
Exemples
Correct
-- Document avec photo dynamique (base64 ou URL)
function showDocumentWithPhoto(source, docType)
local xPlayer = ESX.GetPlayerFromId(source)
-- Récupération photo depuis un service externe ou générée localement
local photoUrl = exports['vms_documents']:GetPlayerPhoto(xPlayer.getIdentifier())
showDocument(source, docType, photoUrl, nil)
end
-- Commande RP
RegisterCommand('showid', function(source)
showDocument(source, 'id', nil, nil)
end, false)
RegisterCommand('showlicense', function(source, args)
local licenseType = args[1] -- 'drive', 'weapon', 'hunt'
if not licenseType then
TriggerClientEvent('ox_lib:notify', source, { type = 'error', description = 'Usage: /showlicense [type]' })
return
end
showDocument(source, licenseType, nil, nil)
end, false)
Incorrect
-- Le client envoie ses propres données → spoof facile
RegisterNetEvent('document:show')
AddEventHandler('document:show', function(docData)
-- Le client envoie firstname, lastname, etc.
-- Pas de vérification SQL → n'importe qui peut se faire passer pour quelqu'un d'autre
for _, playerId in ipairs(ESX.GetPlayers()) do
TriggerClientEvent('document:display', playerId, docData)
end
end)
Références
- vms_documents/server/server.lua L1-95
- vms_documents/client/client.lua L1-80