Por que as fechaduras das portas são importantes no RPG
Os sistemas de fechadura de porta são um dos componentes mais subestimados, porém essenciais, de qualquer servidor de RPG FiveM sério. Sem o gerenciamento adequado das portas, os jogadores podem entrar em arsenais da polícia, salas de funcionários de hospitais e esconderijos de gangues sem restrições, quebrando completamente a imersão. Um sistema de fechadura de porta bem implementado permite que os proprietários de servidores controlem o acesso a todos os interiores do mapa, desde edifícios governamentais até empresas de propriedade dos jogadores. Ele cria limites naturais que impulsionam interações de roleplay, como trocas de chaves, cenários de arrombamento e violações coordenadas. Neste guia, vamos percorrer a construção de um sistema completo de fechadura de porta usando ox_doorlock como base e, em seguida, estendê-lo com mecânica de arrombamento personalizada, autenticação de cartão-chave, grupos de portas e controle de acesso específico para negócios.
Configurando ox_doorlock
O recurso ox_doorlock é a solução de fechadura de porta mais amplamente adotada no ecossistema FiveM, oferecendo uma API limpa, interface de administração integrada para colocação de portas e suporte para portas duplas e portas animadas no estilo garagem. Antes de escrever qualquer código personalizado, você precisa de uma base sólida. Instale ox_doorlock junto com ox_lib, do qual depende para componentes de UI e funções utilitárias. Seu server.cfg precisa garantir que os recursos sejam iniciados na ordem correta, com seu banco de dados sendo executado primeiro, depois ox_lib e, finalmente, ox_doorlock. Uma vez executado, o comando admin integrado permite que você vá até qualquer porta no mundo do jogo e registre-a com um clique.
-- server.cfg load order
ensure oxmysql
ensure ox_lib
ensure ox_doorlock
-- Grant admin access for door placement
add_ace group.admin command.doorlock allowDepois de registrar as portas por meio da interface administrativa, ox_doorlock armazena cada entrada no banco de dados com coordenadas, rumo, hash do modelo e estado do bloqueio. Você pode consultá-los e manipulá-los programaticamente. O recurso expõe exportações e eventos para alternar bloqueios, verificar o estado e gerenciar o acesso. Compreender a estrutura de dados é fundamental antes de criar recursos personalizados sobre ela.
-- 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)Construindo um Mecânico de Arrombamento
Lockpicking adiciona uma camada de jogo que torna as portas trancadas significativas para personagens criminosos. Em vez de as portas serem barreiras absolutas, tornam-se desafios baseados em competências. A melhor abordagem é um minijogo que requer tempo e precisão, fazendo com que o resultado pareça conquistado e não aleatório. Usaremos o sistema de verificação de habilidade integrado do ox_lib para criar um fluxo de arrombamento que exige que o jogador tenha um item de arrombamento em seu inventário, acione uma animação e execute uma verificação de habilidade em vários estágios. Falhar na verificação de habilidade deve ter consequências como quebrar o lockpick ou alertar a polícia próxima por meio de um evento de despacho.
-- 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
endNo lado do servidor, você precisa validar a tentativa de arrombamento, remover o item de arrombamento em caso de falha, destrancar a porta em caso de sucesso e, opcionalmente, enviar um alerta policial. A limitação de taxa é importante aqui para evitar tentativas de spam. Armazene um tempo de espera por jogador por porta para que eles não possam tentar novamente imediatamente após uma falha. O manipulador do servidor também deve verificar se o jogador está realmente perto da porta que afirma estar abrindo, evitando a exploração remota.
-- 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)Implementando um sistema de cartão-chave
Os cartões-chave fornecem um mecanismo de controle de acesso mais estruturado em comparação com itens-chave simples. Eles funcionam bem para edifícios corporativos, instalações governamentais e áreas restritas onde o acesso deve estar vinculado a níveis de liberação específicos. O sistema de cartão-chave atribui um nível de segurança de 1 a 5 para cada porta, e os jogadores devem possuir um cartão-chave com autorização igual ou superior para desbloqueá-la. Isso cria uma hierarquia natural onde um cartão-chave de Nível 3 abre todas as portas de Nível 1, 2 e 3, mas não pode acessar as áreas restritas de Nível 4 ou 5. Os cartões-chave podem ser emitidos por sistemas de trabalho, encontrados como saque ou criados por meio de um sistema de progressão.
-- 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
endGrupos de portas e acesso comercial
O gerenciamento de portas individuais torna-se impossível de escalar quando seu servidor tem centenas de portas trancadas. Os grupos de portas resolvem isso permitindo atribuir várias portas a um grupo nomeado e controlar o acesso a todo o grupo de uma só vez. Uma delegacia de polícia pode ter 15 portas trancadas que devem ser acessíveis pelo trabalho policial. Em vez de configurar cada porta individualmente, você atribui todas elas ao grupo "delegacia_policial" e concede acesso com base no nome do trabalho. Quando um administrador contrata um novo oficial, ele automaticamente obtém acesso a todas as portas do grupo, sem qualquer configuração manual.
-- 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
endAs portas comerciais acrescentam outra dimensão ao vincular o acesso da porta aos registros de propriedade. Quando um jogador compra um negócio, ele deve ganhar automaticamente o controle de todas as portas associadas a essa propriedade. O sistema precisa verificar a propriedade da empresa em tempo real porque as propriedades podem ser vendidas ou transferidas. Implemente um retorno de chamada que consulte sua tabela de negócios para verificar o proprietário atual e, em seguida, armazene em cache o resultado com um TTL curto para evitar sobrecarregar o banco de dados em cada interação de porta.
Sincronizando estados de porta entre clientes
A sincronização de portas é um dos aspectos mais complicados de um sistema de fechadura de porta. Quando um jogador destranca uma porta, todos os jogadores próximos precisam vê-la aberta. Os controles de porta nativos FiveM operam por cliente, o que significa que cada cliente gerencia independentemente os estados da porta. Sem a sincronização adequada, um jogador vê uma porta aberta enquanto outro a vê fechada. ox_doorlock lida com a sincronização básica de estado, mas as extensões personalizadas precisam ter cuidado com a propagação de estado. Use bolsas de estado ou eventos autorizados pelo servidor para transmitir mudanças de porta a todos os jogadores dentro de um intervalo razoável. Evite sincronizar com todo o servidor para cada alternância de porta, pois isso cria sobrecarga de rede desnecessária em servidores grandes com centenas de portas.
-- 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)Considerações de segurança e anti-exploração
Os sistemas de fechadura de porta são um alvo comum para os exploradores porque contornar uma porta muitas vezes concede acesso a itens, armas ou dinheiro restritos. Cada solicitação de alternância de porta deve ser validada no lado do servidor. Nunca confie em IDs de porta enviados pelo cliente sem verificar a proximidade e os direitos de acesso do jogador. Implemente o registro para cada mudança de estado de porta para que os administradores possam revisar padrões suspeitos, como um jogador destravando portas de cofres aos quais eles não deveriam ter acesso. Limite a taxa de todas as interações de porta para evitar tentativas de força bruta no sistema de arrombamento. Considere adicionar um registrador de eventos no servidor que registre o Steam ID do jogador, o ID da porta, o carimbo de data/hora e o método de acesso para trilhas de auditoria. Esses registros tornam-se inestimáveis ao investigar explorações ou resolver disputas entre jogadores sobre quem acessou o quê e quando.
Juntando tudo
Um sistema de fechadura de porta pronto para produção combina todos esses componentes em uma estrutura unificada. O ponto de entrada é uma interação do sistema alvo, seja ox_target ou qb-target, que detecta quando um jogador olha para uma porta registrada e apresenta opções contextuais com base em seu nível de acesso. Se eles tiverem acesso ao trabalho, mostre um botão de desbloqueio. Se eles tiverem um cartão-chave, mostre uma opção de deslizar. Se eles tiverem um lockpick e a porta estiver sinalizada como arrombável, mostre a opção lockpick. Cada caminho alimenta o manipulador apropriado, que é validado no servidor e sincroniza o resultado com clientes próximos. A configuração deve ser orientada por dados por meio de entradas de banco de dados ou arquivos de configuração compartilhados para que os proprietários de servidores possam adicionar, remover e modificar o acesso à porta sem alterar o código. Essa arquitetura modular significa que você pode trocar o minijogo de arrombamento, adicionar novos métodos de acesso, como scanners biométricos, ou integrar sistemas de inventário totalmente diferentes sem reescrever a lógica central.
