Explorer
KNOW-PAT-105

FiveM - Dynamic Document Generation : ID, permis, requete SQL joueur, envoi proximite

Domaine
fivem
Type
pattern
Priorité
P2

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