Назад к блогу
Tutorial9 мин чтения

FiveM Система запирания дверей: скрипт безопасных дверей и ключей

Добавьте дверные замки на любой сервер FiveM. Конфигурация, ключи, двери только для определённых профессий и лучшие скрипты дверных замков для полицейских участков, домов и бизнес-интерьеров.

Agency Scripts

Основатель и ведущий разработчик Agency Scripts

Почему замки на дверях важны в ролевой игре

Системы запирания дверей , одна из самых недооцененных, но при этом ключевых составляющих любого серьезного FiveM ролевого сервера. Без правильного управления дверями игроки могут свободно заходить в полицейские арсеналы, больничные помещения и логова банд, что полностью разрушает погружение. Хорошо реализованная система запирания дверей позволяет владельцам серверов контролировать доступ ко всем интерьерам на карте, от государственных зданий до бизнесов игроков. Она создает естественные границы, стимулирующие ролевые взаимодействия, такие как обмен ключами, сценарии взлома и скоординированные проникновения. В этом руководстве мы пройдемся по созданию полной системы запирания дверей на базе ox_doorlock, а затем расширим ее кастомными механиками взлома, аутентификацией по ключ-картам, группами дверей и контролем доступа для бизнеса.

Настройка ox_doorlock

Ресурс ox_doorlock , самое широко используемое решение для дверных замков в экосистеме FiveM, предлагающее чистый API, встроенный админский UI для размещения дверей и поддержку как двойных дверей, так и анимированных дверей гаражного типа. Перед написанием кастомного кода нужна надежная база. Установите ox_doorlock вместе с ox_lib, от которого он зависит для UI-компонентов и утилит. Ваш server.cfg требуется обеспечить запуск ресурсов в правильном порядке: сначала ваша база данных, затем ox_lib и, наконец, ox_doorlock. После запуска встроенная команда администратора позволяет подойти к любой двери в игровом мире и зарегистрировать её одним кликом.

-- 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

После регистрации дверей через админ-интерфейс ox_doorlock сохраняет каждую запись в базе данных с координатами, направлением, хэшем модели и состоянием замка. Вы можете программно запрашивать и управлять ими. Ресурс предоставляет как экспорты, так и события для переключения замков, проверки состояния и управления доступом. Понимание структуры данных критично перед созданием пользовательских функций на её основе.

-- 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)

Создание механики взлома замков

Взлом замков добавляет игровой слой, который делает запертые двери значимыми для криминальных персонажей. Вместо того чтобы двери были абсолютными преградами, они становятся задачами, требующими навыков. Лучший подход , мини-игра, требующая точного времени и аккуратности, благодаря чему результат ощущается заслуженным, а не случайным. Мы используем встроенную систему skillcheck из ox_lib для создания процесса взлома замков, который требует наличия предмета lockpick в инвентаре игрока, запускает анимацию и выполняет многоступенчатую проверку навыков. Провал skillcheck должен иметь последствия, например, ломать lockpick или оповещать ближайшую полицию через событие dispatch.

-- 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

На стороне сервера необходимо проверить попытку взлома замка, удалить отмычку при неудаче, открыть дверь при успехе и при необходимости отправить сигнал полиции. Важно ограничивать частоту попыток, чтобы предотвратить спам. Храните таймаут для каждого игрока и каждой двери, чтобы они не могли сразу повторить попытку после неудачи. Обработчик сервера также должен проверять, что игрок действительно находится рядом с дверью, которую пытается вскрыть, предотвращая удалённую эксплуатацию.

-- 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)

Реализация системы ключ-карт

Ключ-карты обеспечивают более структурированный механизм контроля доступа по сравнению с простыми ключевыми предметами. Они хорошо подходят для корпоративных зданий, правительственных объектов и зон с ограниченным доступом, где доступ должен быть привязан к определенным уровням допуска. Система ключ-карт присваивает каждой двери уровень безопасности от 1 до 5, и игроки должны иметь ключ-карту с равным или более высоким уровнем допуска для открытия. Это создает естественную иерархию, где ключ-карта уровня 3 открывает все двери уровней 1, 2 и 3, но не дает доступа к зонам уровней 4 и 5. Ключ-карты могут выдаваться через системы профессий, находиться в луте или изготавливаться через систему прогрессии.

-- 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

Группы дверей и доступ для бизнеса

Управление отдельными дверями становится не масштабируемым, когда на вашем сервере сотни запертых дверей. Группы дверей решают эту проблему, позволяя назначать несколько дверей в именованную группу и контролировать доступ ко всей группе сразу. В полицейском участке может быть 15 запертых дверей, к которым должен иметь доступ весь полицейский персонал. Вместо настройки каждой двери по отдельности, вы назначаете их все в группу "police_station" и предоставляете доступ на основе названия работы. Когда администратор нанимает нового офицера, он автоматически получает доступ ко всем дверям группы без ручной настройки.

-- 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

Двери бизнеса добавляют новое измерение, связывая доступ к дверям с записями о владении. Когда игрок покупает бизнес, он автоматически получает контроль над всеми дверями, связанными с этим объектом. Система должна проверять владение бизнесом в реальном времени, так как объекты могут продаваться или передаваться. Реализуйте callback, который запрашивает вашу таблицу бизнеса для проверки текущего владельца, затем кешируйте результат с коротким TTL, чтобы избежать чрезмерных запросов к базе данных при каждом взаимодействии с дверью.

Синхронизация состояния дверей между клиентами

Синхронизация дверей , одна из самых сложных частей системы запирания дверей. Когда один игрок открывает дверь, все близлежащие игроки должны видеть ее открытой. Нативные контролы дверей FiveM работают на стороне каждого клиента отдельно, то есть каждый клиент самостоятельно управляет состоянием дверей. Без правильной синхронизации один игрок видит дверь открытой, а другой , закрытой. ox_doorlock обрабатывает базовую синхронизацию состояний, но кастомные расширения должны аккуратно распространять изменения. Используйте state bags или серверные авторитетные события для трансляции изменений дверей всем игрокам в разумном радиусе. Избегайте синхронизации на весь сервер при каждом переключении двери, так как это создает ненужную нагрузку на сеть на больших серверах с сотнями дверей.

-- 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)

Вопросы безопасности и анти-эксплойт

Системы запирания дверей часто становятся целью эксплойтеров, так как обход двери часто дает доступ к ограниченным предметам, оружию или деньгам. Каждый запрос на переключение двери должен проверяться на стороне сервера. Никогда не доверяйте ID дверей, отправленным клиентом, без проверки близости игрока и прав доступа. Реализуйте логирование каждого изменения состояния двери, чтобы администраторы могли анализировать подозрительные действия, например, когда игрок открывает двери хранилищ, к которым у него не должно быть доступа. Ограничьте частоту всех взаимодействий с дверями, чтобы предотвратить перебор попыток взлома. Рассмотрите возможность добавления серверного логгера событий, который записывает Steam ID игрока, ID двери, временную метку и способ доступа для аудита. Эти логи становятся незаменимыми при расследовании эксплойтов или разрешении споров между игроками о том, кто и когда получил доступ к чему.

Объединение всего вместе

Готовая к продакшену система замков дверей объединяет все компоненты в единую структуру. Точка входа , взаимодействие с системой target, будь то ox_target или qb-target, которая определяет, когда игрок смотрит на зарегистрированную дверь, и показывает контекстные опции в зависимости от уровня доступа. Если у игрока есть доступ по работе, показывается кнопка разблокировки. Если есть ключ-карта , опция свайпа. Если есть отмычка и дверь помечена как взламываемая , опция отмычки. Каждый путь ведет к соответствующему обработчику, который проверяет на сервере и синхронизирует результат с ближайшими клиентами. Конфигурация должна быть основана на данных через записи в базе или общие конфиги, чтобы владельцы серверов могли добавлять, удалять и изменять доступ к дверям без правок кода. Такая модульная архитектура позволяет заменить мини-игру взлома, добавить новые методы доступа, например биометрические сканеры, или интегрироваться с другими системами инвентаря без переписывания основной логики.

Готовы начать?

Возьмите скрипты в нашем магазине или заходите в Discord за поддержкой, обновлениями и анонсами.