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.
