Retour au cours

games / fivem

Items & inventaire : concepts avancés

Leçon 151 exercice

Explication

Ce que vous allez apprendre

  • Séparer une configuration statique d'items des données de possession par joueur
  • Valider chaque ajout d'item (poids maximum, unicité) avant de l'appliquer
  • Valider chaque retrait d'item (quantité réellement possédée) avant de l'appliquer
  • Construire un système d'objets "utilisables" extensible sans modifier le code central
  • Identifier pourquoi l'inventaire est une cible privilégiée de la triche

Dans quel contexte ?

Un joueur tente d'utiliser une faille pour dupliquer un objet rare en envoyant deux requêtes de retrait d'objet presque simultanément, en espérant que le serveur ne les traite pas correctement. Un système d'inventaire qui revalide systématiquement chaque mutation — jamais une seule exception — empêche structurellement ce type d'exploit, contrairement à un système qui ferait confiance à l'état affiché côté client.

ConceptRôle
Config.ItemsConfiguration statique : poids, stackable, unique
Données par joueurQuantité réellement possédée de chaque item
Event technologik:item:<nom>Handler dédié par objet utilisable, facilement extensible

Piège fréquent

Sauter la validation d'une seule mutation d'inventaire (même "juste pour tester rapidement") ouvre une brèche qu'un joueur malveillant peut chercher activement à exploiter pour dupliquer des objets rares ou dépasser le poids maximum autorisé.

L'inventaire, un cas d'école de la persistance

Après avoir vu comment sauvegarder des données joueur en général, cette leçon s'attaque à l'un des systèmes les plus manipulés d'un serveur RP : l'inventaire. C'est aussi l'un des plus exposés aux tentatives de triche, car il touche directement à la valeur perçue du jeu (posséder des objets rares, de l'équipement précieux).

Séparer la définition des objets de leur possession

Un bon système d'inventaire distingue deux choses : une configuration statique, qui décrit une fois pour toutes ce qu'est chaque type d'objet (son poids, s'il est empilable, unique...), et les données par joueur, qui indiquent combien chaque joueur possède de chaque objet. Cette séparation évite de dupliquer les règles à chaque endroit du code où un objet est manipulé, et centralise leur définition en un seul point facile à maintenir.

Pourquoi valider à chaque mutation, sans exception

Chaque fois qu'un objet est ajouté ou retiré, il faut revérifier les règles : le joueur a-t-il assez de place (poids maximum) ? Possède-t-il réellement la quantité qu'il prétend retirer ? Un objet marqué "unique" n'est-il pas déjà possédé ? Sauter ces vérifications ne serait-ce qu'une seule fois, à un seul endroit du code, ouvre une brèche potentielle qu'un joueur malveillant peut chercher activement à exploiter pour dupliquer des objets.

Le modèle des objets "utilisables"

Plutôt que d'écrire une grande fonction qui gère tous les objets utilisables avec une longue liste de conditions, le pattern montré ici déclenche un event nommé dynamiquement selon l'objet utilisé (technologik:item:lockpick). Cela permet d'ajouter facilement un nouvel objet utilisable en créant simplement un nouveau handler dédié, sans jamais devoir modifier le code central existant — un principe de conception qui facilite grandement la maintenance à long terme.

Commandes & code

Items & inventaire

Un système d'inventaire robuste combine une config statique, un stockage par joueur, et une validation stricte à chaque mutation.

lua
-- Config partagée : définition statique des items disponibles
Config.Items = {
    bread = { label = 'Pain', weight = 200, stackable = true, unique = false },
    lockpick = { label = 'Pince à crocheter', weight = 100, stackable = true, unique = false, usable = true },
    id_card = { label = "Carte d'identité", weight = 10, stackable = false, unique = true },
}

Config.MaxWeight = 24000 -- en grammes, par exemple
lua
-- Ajouter un item : TOUJOURS valider côté serveur (poids max, existence de l'item...)
local function AddItem(source, itemName, count)
    local itemConfig = Config.Items[itemName]
    if not itemConfig then return false, 'Item inconnu' end

    local inventory = GetPlayerInventory(source)
    local currentWeight = CalculateInventoryWeight(inventory)
    local addedWeight = itemConfig.weight * count

    if currentWeight + addedWeight > Config.MaxWeight then
        return false, 'Inventaire trop lourd'
    end

    if itemConfig.unique and inventory[itemName] then
        return false, 'Item unique déjà possédé'
    end

    inventory[itemName] = (inventory[itemName] or 0) + count
    SaveInventory(source, inventory)
    return true
end
lua
-- Retirer un item, avec vérification de quantité disponible
local function RemoveItem(source, itemName, count)
    local inventory = GetPlayerInventory(source)
    local current = inventory[itemName] or 0

    if current < count then
        return false, 'Quantité insuffisante'
    end

    inventory[itemName] = current - count
    if inventory[itemName] <= 0 then
        inventory[itemName] = nil
    end

    SaveInventory(source, inventory)
    return true
end
lua
-- Utiliser un item consommable (ex: lockpick pour crocheter une porte)
RegisterNetEvent('technologik:useItem', function(itemName)
    local src = source
    local inventory = GetPlayerInventory(src)

    if not inventory[itemName] or inventory[itemName] <= 0 then
        return -- le joueur n'a pas l'item : on ignore silencieusement (anti-triche)
    end

    local itemConfig = Config.Items[itemName]
    if itemConfig and itemConfig.usable then
        TriggerEvent('technologik:item:' .. itemName, src)
    end
end)

AddEventHandler('technologik:item:lockpick', function(source)
    local ok = RemoveItem(source, 'lockpick', 1) -- consommé à l'usage
    if ok then
        TriggerClientEvent('technologik:startLockpickMinigame', source)
    end
end)

Résumé

  • Une config statique d'items évite de dupliquer des règles (poids, stackable) dans chaque script.
  • Chaque mutation d'inventaire (ajout/retrait) doit revalider les règles côté serveur, sans exception.
  • Les items "usable" déclenchent un event dédié par item, facile à étendre sans toucher au coeur du système.

Exercices pratiques

1 disponible
1

Mission : ajouter un item sans casser l'inventaire

Objectif : Comprendre pourquoi la validation d'inventaire doit rester côté serveur, ajouter un nouvel item utilisable, puis raisonner sur un scénario de duplication.

Contexte

L'inventaire de technologik_rp fonctionne bien pour les objets existants (bread, lockpick, id_card), mais l'équipe design veut ajouter un bandage de soin. Un développeur propose au passage de déplacer la vérification du poids maximum uniquement dans l'interface NUI côté client, "pour que ce soit plus réactif visuellement".

Résoudre l’exercice →