>
Tutoriel 2026-04-16

Système de verrouillage des portes pour FiveM

TDYSKY

TDYSKY

Fondateur et développeur principal chez Agency Scripts

Pourquoi les serrures de porte sont importantes dans le jeu de rôle

Les systèmes de verrouillage de porte sont l'un des composants les plus sous-estimés mais essentiels de tout serveur de jeu de rôle FiveM sérieux. Sans une bonne gestion des portes, les joueurs peuvent entrer sans restriction dans les armureries de la police, les salles du personnel des hôpitaux et les cachettes des gangs, rompant complètement l'immersion. Un système de verrouillage de porte bien mis en œuvre permet aux propriétaires de serveurs de contrôler l'accès à chaque intérieur de la carte, des bâtiments gouvernementaux aux entreprises appartenant aux joueurs. Cela crée des limites naturelles qui déterminent les interactions de jeu de rôle telles que les échanges de clés, les scénarios de crochetage et les violations coordonnées. Dans ce guide, nous allons détailler la création d'un système de serrure de porte complet en utilisant ox_doorlock comme base, puis l'étendre avec des mécanismes de crochetage personnalisés, l'authentification par carte-clé, des groupes de portes et un contrôle d'accès spécifique à l'entreprise.

Configuration de ox_doorlock

La ressource ox_doorlock est la solution de serrure de porte la plus largement adoptée dans l'écosystème FiveM, offrant un API propre, une interface utilisateur d'administration intégrée pour placer les portes et la prise en charge des portes doubles et des portes animées de style garage. Avant d’écrire un code personnalisé, tu as besoin d’une base solide. Installez ox_doorlock aux côtés de ox_lib, dont il dépend pour les composants de l'interface utilisateur et les fonctions utilitaires. Ton server.cfg doit s'assurer que les ressources démarrent dans le bon ordre, avec ton base de données exécutée en premier, puis ox_lib et enfin ox_doorlock. Une fois exécutée, la commande d'administration intégrée tu permet d'accéder à n'importe quelle porte du monde du jeu et de l'enregistrer en un clic.

-- server.cfg load order
ensure oxmysql
ensure ox_lib
ensure ox_doorlock

-- Grant admin access for door placement
add_ace group.admin command.doorlock allow

Après avoir enregistré les portes via l'interface d'administration, ox_doorlock stocke chaque entrée dans la base de données avec les coordonnées, le titre, le hachage du modèle et l'état du verrouillage. Tu peux les interroger et les manipuler par programmation. La ressource expose à la fois les exportations et les événements pour activer les verrous, vérifier l'état et gérer l'accès. Comprendre la structure des données est essentiel avant de créer des fonctionnalités personnalisées par-dessus.

-- Checking if a specific door is locked
local doorId = 1
local isLocked = exports.ox_doorlock:getDoorState(doorId)

-- Toggle a door lock from server side
exports.ox_doorlock:setDoorState(doorId, not isLocked)

-- Listen for door state changes
AddEventHandler('ox_doorlock:stateChanged', function(id, state, source)
    print(('Door %d changed to %s by player %s'):format(id, state and 'locked' or 'unlocked', source))
end)

Construire un mécanisme de crochetage

Lockpicking ajoute une couche de gameplay qui donne du sens aux portes verrouillées pour les personnages criminels. Au lieu d’être des obstacles absolus, les portes deviennent des défis basés sur les compétences. La meilleure approche est un mini-jeu qui nécessite du timing et de la précision, ce qui donne l’impression que le résultat est mérité plutôt qu’aléatoire. Nous utiliserons le système de contrôle de compétences intégré de ox_lib pour créer un flux de crochetage qui oblige le joueur à avoir un objet de crochetage dans son inventaire, déclenche une animation et exécute un contrôle de compétences en plusieurs étapes. L'échec du contrôle de compétence devrait avoir des conséquences telles que briser le crochet ou alerter la police à proximité via un événement de répartition.

-- client/lockpicking.lua
local function AttemptLockpick(doorId)
    local hasLockpick = exports.ox_inventory:Search('count', 'lockpick')
    if hasLockpick < 1 then
        lib.notify({ title = 'No Lockpick', type = 'error' })
        return
    end

    -- Play lockpicking animation
    local ped = PlayerPedId()
    TaskPlayAnim(ped, 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@', 'machinic_loop_mechandlenry', 8.0, -8.0, -1, 1, 0, false, false, false)

    -- Multi-stage skillcheck: easy, medium, hard
    local success = lib.skillCheck({'easy', 'medium', 'hard'}, {'w', 'a', 's', 'd'})

    ClearPedTasks(ped)

    if success then
        TriggerServerEvent('doorlock:lockpick:success', doorId)
        lib.notify({ title = 'Lock Picked', description = 'The door clicks open.', type = 'success' })
    else
        TriggerServerEvent('doorlock:lockpick:fail', doorId)
        lib.notify({ title = 'Failed', description = 'The lockpick snapped.', type = 'error' })
    end
end

Côté serveur, tu dois valider la tentative de crochetage, supprimer l'élément de crochetage en cas d'échec, déverrouiller la porte en cas de succès et éventuellement envoyer une alerte à la police. La limitation du débit est ici importante pour empêcher les tentatives de spam. Enregistrez un temps de recharge par joueur et par porte afin qu'ils ne puissent pas réessayer immédiatement après un échec. Le gestionnaire du serveur doit également vérifier que le joueur se trouve bien à proximité de la porte qu'il prétend choisir, empêchant ainsi toute exploitation à distance.

-- server/lockpicking.lua
local cooldowns = {}

RegisterNetEvent('doorlock:lockpick:success', function(doorId)
    local src = source
    local playerCoords = GetEntityCoords(GetPlayerPed(src))

    -- Verify proximity to door (anti-cheat)
    local doorData = exports.ox_doorlock:getDoor(doorId)
    if not doorData then return end
    if #(playerCoords - doorData.coords) > 3.0 then return end

    -- Check cooldown
    local key = ('%s:%s'):format(src, doorId)
    if cooldowns[key] and os.time() - cooldowns[key] < 30 then return end

    exports.ox_doorlock:setDoorState(doorId, 0) -- Unlock
    cooldowns[key] = os.time()

    -- Auto-relock after 60 seconds
    SetTimeout(60000, function()
        exports.ox_doorlock:setDoorState(doorId, 1)
    end)
end)

RegisterNetEvent('doorlock:lockpick:fail', function(doorId)
    local src = source
    exports.ox_inventory:RemoveItem(src, 'lockpick', 1)

    -- Alert police dispatch
    TriggerEvent('dispatch:alert', {
        coords = GetEntityCoords(GetPlayerPed(src)),
        message = 'Attempted break-in reported',
        code = '10-31',
        job = 'police'
    })
end)

Implémentation d'un système de carte-clé

Les cartes-clés offrent un mécanisme de contrôle d'accès plus structuré que les éléments clés simples. Ils fonctionnent bien pour les bâtiments d'entreprise, les installations gouvernementales et les zones restreintes où l'accès doit être lié à des niveaux d'autorisation spécifiques. Le système de carte-clé attribue un niveau de sécurité de 1 à 5 à chaque porte, et les joueurs doivent posséder une carte-clé avec une autorisation égale ou supérieure pour la déverrouiller. Cela crée une hiérarchie naturelle dans laquelle une carte-clé de niveau 3 ouvre toutes les portes de niveau 1, 2 et 3 mais ne peut pas accéder aux zones restreintes de niveau 4 ou 5. Les cartes-clés peuvent être émises par des systèmes de travail, trouvées comme butin ou fabriquées via un système de progression.

-- shared/config.lua
Config = {}
Config.KeycardLevels = {
    { name = 'keycard_1', label = 'Green Keycard',  level = 1 },
    { name = 'keycard_2', label = 'Blue Keycard',   level = 2 },
    { name = 'keycard_3', label = 'Yellow Keycard', level = 3 },
    { name = 'keycard_4', label = 'Red Keycard',    level = 4 },
    { name = 'keycard_5', label = 'Black Keycard',  level = 5 },
}

Config.DoorSecurity = {
    [10] = { level = 1, name = 'Office Lobby' },
    [11] = { level = 2, name = 'Server Room' },
    [12] = { level = 3, name = 'Executive Floor' },
    [13] = { level = 4, name = 'Vault Anteroom' },
    [14] = { level = 5, name = 'Main Vault' },
}

-- client/keycard.lua
local function TryKeycardAccess(doorId)
    local security = Config.DoorSecurity[doorId]
    if not security then return false end

    for _, card in ipairs(Config.KeycardLevels) do
        if card.level >= security.level then
            local count = exports.ox_inventory:Search('count', card.name)
            if count > 0 then
                -- Play card swipe animation
                lib.requestAnimDict('anim@heists@keycard@')
                TaskPlayAnim(PlayerPedId(), 'anim@heists@keycard@', 'exit', 5.0, 1.0, -1, 16, 0, 0, 0, 0)
                Wait(1200)
                ClearPedTasks(PlayerPedId())
                return true, card
            end
        end
    end
    return false
end

Groupes de portes et accès aux entreprises

La gestion des portes individuelles devient inévolutive lorsque ton serveur comporte des centaines de portes verrouillées. Les groupes de portes résolvent ce problème en tu permettant d'attribuer plusieurs portes à un groupe nommé et de contrôler l'accès à l'ensemble du groupe en même temps. Un commissariat de police peut avoir 15 portes verrouillées qui devraient toutes être accessibles par le poste de police. Au lieu de configurer chaque porte individuellement, tu les attribuez toutes au groupe « police_station » et accordez l'accès en fonction du nom du travail. Lorsqu'un administrateur embauche un nouvel agent, il accède automatiquement à toutes les portes du groupe sans aucune configuration manuelle.

-- server/door_groups.lua
local DoorGroups = {
    police_station = {
        doors = {1, 2, 3, 4, 5, 6, 7, 8},
        access = {
            { type = 'job', name = 'police', minGrade = 0 },
            { type = 'job', name = 'sheriff', minGrade = 0 },
        }
    },
    pillbox_hospital = {
        doors = {20, 21, 22, 23, 24},
        access = {
            { type = 'job', name = 'ambulance', minGrade = 0 },
            { type = 'job', name = 'doctor', minGrade = 2 },
        }
    },
    vangelico = {
        doors = {30, 31},
        access = {
            { type = 'job', name = 'jeweler', minGrade = 0 },
            { type = 'item', name = 'vangelico_key' },
        }
    },
}

function HasGroupAccess(source, groupName)
    local group = DoorGroups[groupName]
    if not group then return false end

    for _, rule in ipairs(group.access) do
        if rule.type == 'job' then
            local job = GetPlayerJob(source)
            if job and job.name == rule.name and job.grade >= (rule.minGrade or 0) then
                return true
            end
        elseif rule.type == 'item' then
            local count = exports.ox_inventory:GetItem(source, rule.name, nil, true)
            if count and count > 0 then return true end
        end
    end
    return false
end

Les portes commerciales ajoutent une autre dimension en liant l’accès aux portes aux registres de propriété. Lorsqu'un joueur achète une entreprise, il devrait automatiquement prendre le contrôle de toutes les portes associées à cette propriété. Le système doit vérifier la propriété de l'entreprise en temps réel, car les propriétés peuvent être vendues ou transférées. Implémentez un rappel qui interroge ton table métier pour vérifier le propriétaire actuel, puis mettez en cache le résultat avec un court TTL pour éviter de marteler la base de données à chaque interaction de porte.

Synchronisation des états de porte entre les clients

La synchronisation des portes est l’un des aspects les plus délicats d’un système de serrure de porte. Lorsqu'un joueur déverrouille une porte, tous les joueurs à proximité doivent la voir s'ouvrir. Les commandes de porte natives FiveM fonctionnent client par client, ce qui signifie que chaque client gère indépendamment l'état des portes. Sans synchronisation appropriée, un joueur voit une porte ouverte tandis qu'un autre la voit fermée. ox_doorlock gère la synchronisation d'état de base, mais les extensions personnalisées doivent faire attention à la propagation de l'état. Utilisez des sacs d'état ou des événements faisant autorité sur le serveur pour diffuser les changements de porte à tous les joueurs dans une plage raisonnable. Évitez de synchroniser l'ensemble du serveur pour chaque bascule de porte, car cela crée une surcharge réseau inutile sur les grands serveurs comportant des centaines de portes.

-- server: broadcast door state to nearby players only
local function SyncDoorToNearby(doorId, state, coords, range)
    range = range or 100.0
    local players = GetPlayers()
    for _, playerId in ipairs(players) do
        local ped = GetPlayerPed(playerId)
        if ped and DoesEntityExist(ped) then
            local playerCoords = GetEntityCoords(ped)
            if #(playerCoords - coords) <= range then
                TriggerClientEvent('doorlock:sync', tonumber(playerId), doorId, state)
            end
        end
    end
end

-- client: apply synced door state
RegisterNetEvent('doorlock:sync', function(doorId, state)
    local door = exports.ox_doorlock:getDoor(doorId)
    if door then
        door.state = state
    end
end)

Considérations de sécurité et anti-exploit

Les systèmes de verrouillage des portes sont une cible courante pour les exploiteurs, car le contournement d'une porte donne souvent accès à des objets, des armes ou de l'argent restreints. Chaque demande de basculement de porte doit être validée côté serveur. Ne faites jamais confiance aux identifiants de porte envoyés par les clients sans vérifier la proximité et les droits d'accès du joueur. Implémentez la journalisation pour chaque changement d'état de porte afin que les administrateurs puissent examiner les modèles suspects, comme un joueur déverrouillant des portes de coffre-fort auxquelles il ne devrait pas avoir accès. Limitez le taux de toutes les interactions de porte pour empêcher les tentatives de force brute sur le système de crochetage. Pensez à ajouter un enregistreur d'événements côté serveur qui enregistre l'identifiant Steam, l'identifiant de porte, l'horodatage et la méthode d'accès du joueur pour les pistes d'audit. Ces journaux deviennent inestimables pour enquêter sur des exploits ou résoudre des différends entre joueurs sur qui a accédé à quoi et quand.

Rassembler tout cela

Un système de serrure de porte prêt à la production combine tous ces composants dans un cadre unifié. Le point d'entrée est une interaction du système cible, ox_target ou qb-target, qui détecte lorsqu'un joueur regarde une porte enregistrée et présente des options contextuelles en fonction de son niveau d'accès. S'ils ont accès au travail, affichez un bouton de déverrouillage. S'ils ont une carte d'accès, affichez une option de balayage. S'ils ont un crochetage et que la porte est signalée comme pouvant être crochetée, affichez l'option de crochetage. Chaque chemin alimente le gestionnaire approprié, qui valide sur le serveur et synchronise le résultat avec les clients à proximité. La configuration doit être basée sur les données via des entrées de base de données ou des fichiers de configuration partagés afin que les propriétaires de serveurs puissent ajouter, supprimer et modifier l'accès aux portes sans toucher au code. Cette architecture modulaire signifie que tu peux remplacer le mini-jeu de crochetage, ajouter de nouvelles méthodes d'accès telles que des scanners biométriques ou intégrer des systèmes d'inventaire entièrement différents sans réécrire la logique de base.

Partager cet article

Prêt à améliorer votre serveur ?

Découvrez nos scripts FiveM premium dans la boutique Agency Scripts ou rejoignez notre communauté Discord pour le support et les mises à jour.