[GUIDE] Lock doors like a boss. How to use GTA's built-in door locking system

If you want to lock doors, you’ve come to the right place. Many developers seem to use esx_doorlock (which appears over 1,000 times on Github) or a similar system that relies on freezing the door entity’s position to lock a door.

This is lame! esx_doorlock in particular runs every single frame when you’re within 50 units of a door, and every 500ms when you’re not. Grand Theft Auto has a built-in door system that requires no threads to use effectively.

One of the great benefits of DoorSystem is that the player doesn’t need to be near a door for us to change it’s lock state. We can fire and forget, even if they’re thousands of units away from the door, and that setting will be respected when the door does come into scope. No checking of player distance (although you might need to do that in an optimized manner to show lock/unlock prompts, etc), no entity freezing, and no mucking about with headings.

If a door’s state is changed to locked while it’s ajar, it will close behind you and then lock itself. This makes for great autolocking systems where you can have a door setup to automatically lock after five seconds. Players can unlock, dash through the door, and not have to worry about it.

Side note: Sometimes doors can get in a weird state where they get stuck ajar and don’t close. It’s rare, and can be fixed by bumping into it.

Familiarizing yourself with the natives.

There are around 20 natives related to DoorSystem that allow you to do all kinds of interesting things (for example, I have a Pacific Bank heist that uses DoorSystem to both lock the vault door shut, and also lock it open when the door is blown open), but for the sake of this tutorial we’re only going to focus on two of them.

AddDoorToSystem(doorHash, modelHash, x, y, z, p5, scriptDoor, isLocal)

This native is what you will use to register each of your doors. We can toss out the last three parameters, and the first five are pretty self-explanatory. doorHash should be a unique identifier for each door. I usually use an indexed table and use the index for the door hash. modelHash is the model of the door, and x, y, and z are it’s coordinates. You can use a tool like CodeWalker to find this, but my personal favorite is demmycam which includes other useful features as well.

DoorSystemSetDoorState(doorHash, state, requestDoor, forceUpdate)

Surprisingly self explanatory on this one as well. We can toss out the last two parameters for our simple use case as well. doorHash is the hash we invented earlier, and state can be 0 for unlocked, or 2 for locked. There are some other states as well, but they are outside of the scope of this guide.

Putting it all to work.

Alright, so hopefully we’ve identified a door we want to use. For this guide, I’m going to use the front door of the Vanilla Unicorn. Using demmycam, I identified it’s model hash as -1116041313, and it’s coordinates as vector3(127.96, -1298.50, 29.42).

First things first, let’s organize our code into three different files – config.lua, client.lua, and server.lua.This will help us keep things organized.

I’m going to setup my configuration first by specifying all of my doors. I’ll also specify a Locked boolean, which we will use as the default value.

config.lua
Config = {}

Config.Doors = {
    [1] = {
        Model       = -1116041313,
        Coordinates = vector3(127.96, -1298.50, 29.42),
        Locked      = false,
    },
}

Next, I’m going to setup our server script. This is pretty simple. We’ll send the state of all doors to a player when they connect, and we’ll have a command we can use to toggle door lock status. You could probably get fancy and use global state for this, but we’re going to keep it simple.

server.lua
local doorState = Config.Doors

--
-- Commands
--

RegisterCommand('toggleDoor', function(source, args, rawCommand)
    local doorId = tonumber(args[1])

    if not doorId or not doorState[doorId] then
        return
    end

    doorState[doorId].Locked = not doorState[doorId].Locked

    TriggerClientEvent('ch_doorSystem:set', -1, doorId, doorState[doorId].Locked)
end)

--
-- Events
--

AddEventHandler('playerJoining', function()
    TriggerClientEvent('ch_doorSystem:initialize', source, doorState)
end)

With the server side out of the way, let’s wrap up with the real magic on the client.

client.lua

client.lua

--
-- Events
--

RegisterNetEvent('ch_doorSystem:set')
AddEventHandler('ch_doorSystem:set', function(doorId, isLocked)
    DoorSystemSetDoorState(doorId, isLocked and 1 or 0)
end)

RegisterNetEvent('ch_doorSystem:initialize')
AddEventHandler('ch_doorSystem:initialize', function(allDoors)
    for doorId, door in pairs(allDoors) do
        AddDoorToSystem(doorId, door.Model, door.Coordinates)
        DoorSystemSetDoorState(doorId, door.Locked and 1 or 0)
    end
end)

And that’s it, we’re done! Not a single thread in sight. To lock and unlock our new door, just do /toggleDoor [id] where [id] is the ID we specified in our config. In this case, /toggleDoor 1 is what we want to use. Try it from thousands of meters away and see the magic happen.

Thanks!

Thanks for reading! I hope you found this guide informative and that it will help you improve your resource’s door locks. Cheerio!

More reading

Example Resource Download:
ch_doorSystem.zip (1.2 KB)

DoorSystem Natives:

Door System Natives
12 Likes

Excellent work !

also inb4 paid version of your solution

1 Like

Awesome idea and great work!

You’re awesome! Again helping my optimization ocd ahahahahah

1 Like

Are the states synced across the server when setting isLocal to false? Or what is that param doing?

what about some doors only work for a specific job only? like police job can lock unlock pd doors

nui_doorlock is using it :slight_smile:

1 Like

Thx for the info