Explorer
KNOW-PAT-094

FiveM - Status Persistence JSON : sauvegarde/restauration d'etat joueur en MySQL

Domaine
framework
Type
pattern
Priorité
P2

Parent : [[INDEX-FIVEM-FRAMEWORK]]

FiveM - Status Persistence JSON : sauvegarde/restauration d'état joueur en MySQL

Problème

Les états joueur (faim, soif, stress, saleté) doivent persister entre les sessions de jeu. Sans persistence, le joueur recommence à zéro à chaque connexion.

Solution

Sauvegarde/restauration d'état joueur en JSON dans MySQL, chargé sur esx:playerLoaded, sauvegardé sur esx:playerDropped.

-- Chargement au spawn
AddEventHandler('esx:playerLoaded', function(src, xPlayer)
    local status = MySQL.scalar.await('SELECT status FROM users WHERE identifier = ?', {
        xPlayer.getIdentifier()
    })
    status = status and json.decode(status) or {}
    xPlayer.set('status', status)
    TriggerClientEvent('esx_status:load', xPlayer.source, status)
end)

-- Sauvegarde à la déconnexion
AddEventHandler('esx:playerDropped', function(src, reason)
    local xPlayer = ESX.Player(src)
    if not xPlayer then return end
    
    local status = xPlayer.get('status') or {}
    MySQL.update('UPDATE users SET status = ? WHERE identifier = ?', {
        json.encode(status),
        xPlayer.getIdentifier()
    })
end)

-- Tick périodique (toutes les 5 minutes)
CreateThread(function()
    while true do
        Wait(300000)
        local players = ESX.GetPlayers()
        for i = 1, #players do
            local xPlayer = ESX.GetPlayerFromId(players[i])
            local status = xPlayer.get('status') or {}
            MySQL.update('UPDATE users SET status = ? WHERE identifier = ?', {
                json.encode(status),
                xPlayer.getIdentifier()
            })
        end
    end
end)

Pourquoi

  • JSON permet de stocker un objet arbitraire (faim, soif, stress, maladie, addictions) dans une seule colonne
  • scalar.await évite les callbacks imbriqués
  • Sauvegarde périodique + au drop garantit la cohérence même en cas de crash

Quand l'utiliser

  • Besoins complexes (faim/soif/stress/santé mentale)
  • Systèmes de progression long terme (gym, addiction, fatigue)

Quand NE PAS l'utiliser

  • Un seul statut simple → colonne dédiée (hunger INT) plus performante
  • Si les stats ne doivent pas persister (death reset)

Exemples

Correct

-- Structure JSON extensible
{
  "hunger": 85,
  "thirst": 72,
  "stress": 15,
  "hygiene": 90,
  "addictions": {
    "alcohol": 0.2,
    "nicotine": 0.05
  }
}

Incorrect

-- Pas de await → race condition au spawn
AddEventHandler('esx:playerLoaded', function(src, xPlayer)
    MySQL.query('SELECT status FROM users WHERE identifier = ?', {xPlayer.getIdentifier()}, function(result)
        -- Le joueur peut déjà être en jeu avant que ce callback ne resolve
        TriggerClientEvent('esx_status:load', src, result[1].status)
    end)
end)

Références

  • esx_status/server/main.lua L1-50