Retour au cours

games / fivem

Migration de scripts ESX vers QBCore

Leçon 241 exercice

Explication

Ce que vous allez apprendre

  • Construire une table de correspondance systématique entre appels ESX et QBCore
  • Identifier les différences structurelles qui ne sont pas de simples renommages
  • Adapter un système d'items pour tenir compte des metadata natives de QBCore
  • Écrire un wrapper de compatibilité temporaire pour migrer progressivement
  • Éviter d'accumuler une dette technique en gardant la double compatibilité trop longtemps

Dans quel contexte ?

Une équipe qui a construit tout un serveur RP sur ESX décide de migrer vers QBCore pour profiter d'un écosystème de ressources communautaires plus actif. Réécrire l'intégralité du code depuis zéro serait à la fois risqué et démesurément long ; cette leçon montre comment aborder la migration comme un exercice méthodique de correspondance entre les deux vocabulaires, plutôt qu'une réécriture complète.

Concept ESXÉquivalent QBCore
ESX.GetPlayerFromIdQBCore.Functions.GetPlayer
xPlayer.addMoney(amount)Player.Functions.AddMoney('cash', amount)
xPlayer.job.grade (nombre)Player.PlayerData.job.grade.level (objet)

Piège fréquent

Migrer un système d'items en se contentant de renommer les appels de fonction, sans intégrer les metadata natives de QBCore (durabilité, numéro de série), produit un code qui compile mais se comporte différemment de ce qui était prévu — une migration bâclée qui ne se voit qu'en test approfondi.

Quand la question n'est plus "lequel choisir" mais "comment changer"

La leçon 11 a comparé ESX et QBCore pour aider à choisir un framework au départ. Cette dernière leçon aborde une situation différente et très concrète : une équipe qui a déjà construit tout un serveur sur l'un des deux, et qui doit désormais migrer vers l'autre — un projet réel, souvent motivé par l'écosystème de ressources plus riche ou plus actif de la cible.

Une migration, c'est du mapping, pas de la réécriture

Le point rassurant de cette leçon : la LOGIQUE métier (vérifier un solde, ajouter un objet) reste globalement identique entre les deux frameworks — c'est essentiellement le VOCABULAIRE des fonctions et leur organisation qui diffère. Construire une table de correspondance systématique entre les appels ESX et leurs équivalents QBCore transforme une tâche qui semble écrasante en un travail méthodique, presque mécanique, plutôt qu'une réécriture complète depuis zéro.

Les différences qui ne sont PAS de simples renommages

Certains écarts entre les deux frameworks ne sont pas juste des noms de fonctions différents : QBCore distingue explicitement l'argent en liquide de l'argent en banque là où ESX les traite souvent de façon plus uniforme, et les objets QBCore embarquent nativement des métadonnées (numéro de série, durabilité) qu'ESX ne gère pas de la même manière. Une migration bâclée qui se contente de renommer les appels sans intégrer ces différences structurelles produira un système qui compile mais se comporte différemment de ce qui était prévu.

Le wrapper de compatibilité : un pont, pas une destination

Faire tourner une double compatibilité (détecter dynamiquement quel framework est présent, comme vu en leçon 11) permet de migrer ressource par ressource plutôt que de tout casser d'un seul coup. Mais c'est une solution de transition : maintenir indéfiniment un code qui supporte les deux frameworks accumule une dette technique croissante, et l'objectif doit rester de terminer la migration plutôt que de la figer à mi-chemin.

Commandes & code

Migration de scripts ESX vers QBCore

ESX et QBCore couvrent les mêmes besoins (framework RP) mais avec des conventions différentes : la migration est surtout une question de mapping systématique, pas de réécriture logique.

lua
-- ESX : récupération de l'objet joueur, style callback historique
ESX.GetPlayerFromId(source)   -- retourne un xPlayer avec ses propres méthodes

RegisterServerEvent('esx_example:giveMoney')
AddEventHandler('esx_example:giveMoney', function(amount)
    local xPlayer = ESX.GetPlayerFromId(source)
    xPlayer.addMoney(amount)              -- API orientée méthodes sur l'objet joueur
    xPlayer.showNotification('Argent reçu')
end)

-- QBCore : équivalent direct, mais convention de nommage et structure différentes
local QBCore = exports['qb-core']:GetCoreObject()

RegisterServerEvent('qb_example:giveMoney')
AddEventHandler('qb_example:giveMoney', function(amount)
    local Player = QBCore.Functions.GetPlayer(source)
    Player.Functions.AddMoney('cash', amount)      -- QBCore distingue explicitement le TYPE de monnaie (cash/bank)
    TriggerClientEvent('QBCore:Notify', source, 'Argent reçu', 'success')
end)
lua
-- Table de correspondance des concepts clés (à garder comme référence pendant toute la migration)
local ESX_TO_QBCORE_MAPPING = {
    -- ESX                              -- QBCore
    ['ESX.GetPlayerFromId']          = 'QBCore.Functions.GetPlayer',
    ['xPlayer.addMoney']             = "Player.Functions.AddMoney('cash', amount)",
    ['xPlayer.getMoney']             = "Player.PlayerData.money['cash']",
    ['xPlayer.getInventoryItem']     = 'Player.Functions.GetItemByName',
    ['xPlayer.job.name']             = 'Player.PlayerData.job.name',
    ['xPlayer.job.grade']            = 'Player.PlayerData.job.grade.level',   -- QBCore: grade est un objet, pas un nombre
    ['ESX.RegisterUsableItem']       = 'QBCore.Functions.CreateUseableItem',
    ['ESX.GetSharedObject (client)'] = "exports['es_extended']:getSharedObject() -> exports['qb-core']:GetCoreObject()",
}
lua
-- Différence structurelle importante : les items. ESX identifie par 'name' string, QBCore ajoute des metadata riches
-- ESX : item simple
xPlayer.addInventoryItem('bread', 1)

-- QBCore : items avec metadata (durabilité, munitions restantes, numéro de série...) natif au framework
Player.Functions.AddItem('weapon_pistol', 1, false, {
    serie = 'A1B2C3',        -- numéro de série unique pour traçabilité (utile en RP police)
    quality = 100,             -- durabilité
    ammo = 12,
})
-- Une migration ESX -> QBCore d'un script d'armurerie doit donc AJOUTER une gestion de metadata, pas juste renommer les appels
lua
-- Wrapper de compatibilité temporaire (pattern recommandé pendant une migration progressive, pas une solution finale)
-- Permet de migrer script par script sans tout casser d'un coup
local Bridge = {}

function Bridge.GetPlayer(source)
    if GetResourceState('qb-core') == 'started' then
        local QBCore = exports['qb-core']:GetCoreObject()
        local Player = QBCore.Functions.GetPlayer(source)
        return {
            addMoney = function(amount) Player.Functions.AddMoney('cash', amount) end,
            getMoney = function() return Player.PlayerData.money['cash'] end,
        }
    elseif GetResourceState('es_extended') == 'started' then
        local ESX = exports['es_extended']:getSharedObject()
        local xPlayer = ESX.GetPlayerFromId(source)
        return {
            addMoney = function(amount) xPlayer.addMoney(amount) end,
            getMoney = function() return xPlayer.getMoney() end,
        }
    end
    error('Aucun framework RP détecté (ni QBCore ni ESX)')
end
-- Cette couche d'abstraction doit être temporaire : la dette technique d'un double support s'accumule vite

Résumé

  • La migration ESX -> QBCore est surtout un exercice de mapping systématique des concepts (joueur, argent, items, job).
  • QBCore distingue explicitement les types de monnaie (cash/bank) là où ESX les traite souvent de façon plus implicite.
  • Les items QBCore embarquent des metadata natives (durabilité, numéro de série) : une vraie migration doit en tenir compte, pas juste renommer les appels.
  • Un wrapper de compatibilité facilite une migration progressive, mais doit rester temporaire pour éviter d'accumuler de la dette technique.

Exercices pratiques

1 disponible
1

Mission : sauver la traçabilité policière après migration

Objectif : Diagnostiquer une perte de metadata après une migration bâclée d'ESX vers QBCore, la corriger, puis évaluer un choix de dette technique.

Contexte

L'équipe de technologik_rp migre son script d'armurerie d'ESX vers QBCore. Le développeur remplace uniquement xPlayer.addInventoryItem('weapon_pistol', 1) par Player.Functions.AddItem('weapon_pistol', 1), sans rien ajouter d'autre. Le script fonctionne sans erreur en test, mais le système de traçabilité policière basé sur le numéro de série des armes cesse totalement de fonctionner après la migration.

Résoudre l’exercice →