en-doorlock locks map doors, single or double, and the server decides who gets through. A locked door opens for players who meet its rules: a job or player group at or above a grade, or carrying any one of a set of items, such as a keycard.
A door can also have a passcode keypad with a cooldown after wrong guesses, a lockpick difficulty (or be unpickable), and an automatic relock timer. Near a door, players see a padlock icon and an E prompt to lock or unlock it.
Admins build doors in game with /doorlock. Aim at a door and press E (press again on the second half of a double door), then Enter, and fill in the settings dialog.
At a glance
| Depends on | oxmysql, en-core, en-ui |
| Start after | All of the above, plus en-inventory for item rules and lockpicking |
| Database tables | encore_doors |
| Config files | config/shared.lua |
| Key binds | en_doorlock_use (E) |
| Target options | en-doorlock:pick on door models (needs a lockpick; door locked and pickable) |
How it works
Access rules
When you press E on a locked door, the server checks, in order:
- If
Config.adminBypassis on and you have thecommand.doorlockACE, the door opens. - Each entry in the door's
groups: a job name with a minimum grade,group:<id>for a specific player group at a minimum grade, or the bare keygroupfor membership of any player group at that grade. - Each item in
items: carrying any one of them is enough.
If none match and the door has a passcode, you're asked for it. A wrong code locks you out for Config.passcodeCooldown seconds.
Lockpicking
Lockpicking uses encore.minigame.lockpick with your lockpick skill bonus from en-skills. A success always unlocks the door and never locks it. The server requires at least 1.5 s and that you are close to the door. A failure uses up the pick with probability loseOnFail. Picking a door awards thievery XP.
Door sources
Doors come from three places:
| Source | Id | Editable in game | Saved |
|---|---|---|---|
Config.doors | config:<index> | No | Config file |
/doorlock editor | Database id | Yes | encore_doors, including lock state |
AddDoor export | Your id | No | Runtime only |
Door state is broadcast to clients by net events and applied with DoorSystemSetDoorState. Door system hashes are joaat('en-doorlock:<id>:<index>').
Configuration
| Key | Default | What it does |
|---|---|---|
Config.keys.use | 'E' | Use key |
Config.adminPrincipal | 'group.admin' | ACE principal allowed to use /doorlock |
Config.adminBypass | false | If true, players allowed command.doorlock open every door |
Config.distance | 2.0 | Default reach (each door is clamped to 1–8) |
Config.showLock | true | Draw a padlock icon on nearby doors |
Config.lockpick | item = 'lockpick', difficulty = 'medium', loseOnFail = 0.35, xp = { thievery = 12 } | Lockpicking settings |
Config.passcodeCooldown | 5 | Seconds locked out after a wrong code |
Config.doors | {} | Doors defined in config |
Door fields
These fields apply to config doors, editor doors and the AddDoor export.
| Field | What it does |
|---|---|
name | Display name, up to 60 characters |
doors | { { model = <hash>, coords = vec3 }, ... }, one or two parts. model must be a number, so use a backtick hash literal |
locked | Starting state (default true) |
distance | Reach for this door |
autolock | Seconds after unlocking before it relocks; nil = never |
groups | { jobName = minGrade, ['group:<id>'] = minGrade, group = minGrade } |
items | { 'item', ... }; carrying any one opens the door |
passcode | String, up to 16 characters |
lockpick | 'easy', 'medium' or 'hard', or false for unpickable; nil uses Config.lockpick.difficulty |
Adding a config door
Config.doors = {
{
name = 'Checkpoint armoury',
doors = { { model = `v_ilev_rc_door2`, coords = vec3(-252.1, -310.4, 21.6) } },
locked = true,
distance = 2.0,
autolock = 30,
groups = { ['group:12'] = 1 },
items = { 'keycard_armoury' },
passcode = '4471',
lockpick = 'hard',
},
}Exports
Server
| Export | Arguments | Returns |
|---|---|---|
GetDoor | id | Public door { id, name, doors, locked, distance, passcode, lockpick } or nil. passcode and lockpick are booleans |
GetDoorByName | name | Public door or nil |
SetDoorLocked | id, locked | boolean (whether the door exists). Reports the change as 'script' |
AddDoor | id, data | Door id or nil. Runtime only, not saved |
RemoveDoor | id | boolean |
Public door data never includes the passcode or the access rules.
Prefix ids passed to AddDoor with your resource name, such as myheist:vault, so they can't clash with other resources.
Client
| Export | Returns |
|---|---|
GetClosestDoor() | The nearby door within reach (on foot), or nil |
UseClosestDoor() | Tries to toggle the nearby door, prompting for a passcode if needed |
PickClosestDoor() | Starts lockpicking the nearby door |
Commands
| Command | Restricted | What it does |
|---|---|---|
/doorlock [new|edit] | Config.adminPrincipal | new (default) picks doors and creates a lock. edit changes or deletes the closest door |
Only editor-made doors can be edited or deleted in game. Change config and export doors where they're defined.
Events
| Event | Side | Payload |
|---|---|---|
en-doorlock:server:stateChanged | Server-local | source?, doorId, locked, how |
how is one of key, passcode, lockpick, auto or script. A player toggling a door they're authorised for, by group or item, reports key.
Database
encore_doors: id (INT auto-increment primary key), data (LONGTEXT JSON of the door fields). Lock state is written back on every change, so editor doors keep it across restarts.
Examples
Add a door from another resource and open it when a heist step completes:
-- server
local doorId = exports['en-doorlock']:AddDoor('myheist:vault', {
name = 'Bunker vault',
doors = { { model = `v_ilev_bk_vaultdoor`, coords = vec3(255.2, 223.9, 102.4) } },
locked = true,
lockpick = false,
items = { 'vault_keycard' },
})
AddEventHandler('myheist:server:hackComplete', function()
exports['en-doorlock']:SetDoorLocked(doorId, false)
end)Log every forced entry:
AddEventHandler('en-doorlock:server:stateChanged', function(source, doorId, locked, how)
if how == 'lockpick' then
local door = exports['en-doorlock']:GetDoor(doorId)
print(('%s picked %s'):format(GetPlayerName(source), door and door.name or doorId))
end
end)