> For the complete documentation index, see [llms.txt](https://docs.valoragaming.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.valoragaming.com/scripts/vlr_doorlock.md).

# vlr\_doorlock — Doorlock

`vlr_doorlock` is a server-authoritative door manager. Every door is created and edited in-game through a Valora administration panel, with a wide range of access rules (jobs, gangs, characters, items, passcodes, public, ACE), optional biometric Face ID and fingerprint scanners, a placeable item detector that physically blocks flagged players behind a laser checkpoint, lockpicking, persistent states, automatic relocking and a security audit log. All authority lives on the server and the framework is auto-detected.

|               |                                                                                                        |
| ------------- | ------------------------------------------------------------------------------------------------------ |
| **Version**   | 1.0.0                                                                                                  |
| **Author**    | Valora                                                                                                 |
| **Framework** | Qbox / QBCore / ESX — auto-detected (only needed for job/gang/admin checks); runs standalone otherwise |
| **Storage**   | oxmysql (`vlr_doorlock` + `vlr_doorlock_audit` tables, self-creating)                                  |
| **Built on**  | ox\_lib + oxmysql                                                                                      |

## Features

* **In-game door editor** — create and edit every door from the `/dooradmin` panel; aim at the exact door object to capture it. No coordinate hunting in config.
* **Single & double doors** — capture one or both leaves of a doorway.
* **Sliding / automatic doors** — flagged doors use a fixed-position Valora HUD with `E` (alongside `ox_target`), so a door object that has moved can always be closed again.
* **Rich access rules** — combine **jobs**, **gangs**, **character IDs**, **items**, **public access**, an **access code (keypad)** and **ACE/admin** on a single door. Jobs and gangs use a `name:minimumGrade` format.
* **Biometric verification** — per-door **Face ID** and/or **fingerprint** profiles, registered against the player's persistent character identifier and validated on the server; a live GTA character portrait renders inside the Face ID scanner. Biometrics can both lock and unlock an eligible door.
* **Placeable biometric scanner** — drop a scanner prop in the world; biometric actions then move from the door model onto that scanner's `ox_target`.
* **Item security detector** — a placeable metal-detector prop with server-side prohibited-item scanning. A laser-barrier checkpoint physically blocks a flagged player until the scan passes; clearance is short-lived and consumed on entry. One-way or two-way scanning.
* **Lockpicking** — optional ox\_lib skill-check lockpicking, gated by configurable lockpick items with a per-item break chance.
* **Private-property management** — assign an online player as the property owner and add editors, each granted only the editing permissions you choose (PIN, Access, Biometrics, Settings).
* **Persistent states & auto-relock** — door state survives restarts (always-locked, always-unlocked, or restore-last-state per door); optional auto-lock timer and synchronized hold-open behavior.
* **Security audit history** — a searchable activity log of locks, unlocks, denials, lockpicks and editor changes, with retention limits; optional Discord webhook.
* **Server-authoritative** — distance, permission and cooldown are all re-validated on the server; passcodes and full access rules are never sent in normal player sync.
* **Localization** — ships in **English**, fully translatable via the open `locales/` files. More languages are planned over time. Config-driven, independent of `ox:locale`.
* **Developer API** — server exports plus a `vlr_doorlock:stateChanged` event for alarms, dispatch, robberies or logging.

## Dependencies

| Dependency                                                    | Required | Notes                                                                                                                                    |
| ------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| [ox\_lib](https://github.com/overextended/ox_lib)             | Yes      | UI, callbacks, skill-checks, commands, notifications.                                                                                    |
| [oxmysql](https://github.com/overextended/oxmysql)            | Yes      | Persists doors and the audit log.                                                                                                        |
| [ox\_target](https://github.com/overextended/ox_target)       | Optional | Used for door / scanner / detector interactions when present (a compact HUD fallback exists).                                            |
| [ox\_inventory](https://github.com/overextended/ox_inventory) | Optional | Item access rules, lockpick consumption and detector item scanning. `qb-inventory` and ESX inventories are also supported by the bridge. |
| Framework (Qbox / QBCore / ESX)                               | Optional | Only for job/gang resolution and group-based admin checks. Auto-detected; works standalone with ACE admin.                               |

{% hint style="info" %}
Only **ox\_lib** and **oxmysql** are hard dependencies (`fxmanifest.lua`). Everything else — `ox_target`, an inventory, and a framework — is auto-detected at runtime and used only if it is running.
{% endhint %}

## Installation

### 1. Start order

Ensure the hard dependencies start **before** `vlr_doorlock`:

```cfg
ensure ox_lib
ensure oxmysql
ensure vlr_doorlock
```

### 2. Drop in the resource

Copy `vlr_doorlock` into `resources/` (we recommend `resources/[valora]/vlr_doorlock`).

### 3. Database

The `vlr_doorlock` and `vlr_doorlock_audit` tables **self-create on first start**. Importing `install/vlr_doorlock.sql` is only needed for manual setups or schema review.

### 4. Grant admin access

Give your administrators the ACE permission:

```cfg
add_ace group.admin vlr.doorlock.admin allow
```

Framework groups in `Config.Admin.groups` (`admin`, `god` by default) are also accepted.

### 5. Create doors

In-game, run `/dooradmin`, create a door, and aim at the door object when the editor asks for a capture.

## Database

Two self-creating tables:

```sql
CREATE TABLE IF NOT EXISTS `vlr_doorlock` (
    `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(80) NOT NULL,
    `data` LONGTEXT NOT NULL,
    `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uq_vlr_doorlock_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS `vlr_doorlock_audit` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `door_id` INT UNSIGNED NULL,
    `door_name` VARCHAR(80) NOT NULL,
    `actor_identifier` VARCHAR(80) NULL,
    `actor_name` VARCHAR(80) NOT NULL,
    `action` VARCHAR(24) NOT NULL,
    `details` VARCHAR(255) NULL,
    `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    KEY `idx_vlr_doorlock_audit_door` (`door_id`),
    KEY `idx_vlr_doorlock_audit_created` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

Each door's full definition (parts, access rules, biometrics, detector, owner/editors) is stored as JSON in the `data` column. Database door names are unique.

## Configuration

Everything lives in the open `config.lua`.

### General

| Key             | Meaning                                                                                                   |
| --------------- | --------------------------------------------------------------------------------------------------------- |
| `Config.Locale` | Active locale (default `'en'`), independent of `ox:locale`. Add more `locales/<code>` files to translate. |
| `Config.Debug`  | Enables debug prints.                                                                                     |
| `Config.UI`     | NUI accent colour, panel `position`, and `showDistance`.                                                  |
| `Config.Notify` | Notification function (defaults to `lib.notify`).                                                         |

### Commands & keys

* `Config.Commands` — command names: `admin` (`dooradmin`), `toggle` (`doorlock`), `lockpick` (`lockpickdoor`).
* `Config.Keys` — interaction keys: `toggle` (`E`), `lockpick` (`G`).

### Interaction

`Config.Interaction` — `scanDistance`, `defaultDistance`, `serverTolerance`, `cooldown`, `drawMarker`, `useTarget` (use `ox_target` when available) and `showHudFallback`.

### Security & lockpicking

* `Config.Security` — keypad brute-force protection: `keypadAttempts`, `keypadLockout` (seconds) and `lockpickTokenLifetime`.
* `Config.Lockpick` — `enabled`, allowed `items` (`lockpick`, `advancedlockpick`), per-item `breakChance`, skill-check `difficulty` and `inputs`.

### Biometrics & detectors

* `Config.BiometricScanner` — scanner prop `model` and interaction `distance`.
* `Config.Detector` — the item-detector / laser-checkpoint behaviour: detector prop `model`, `distance`, `drawRange`, `scanRange`, `wallStop`, `scanTime`, the invisible collision `wallModel` (with `wallHeadingOffset` / `wallZOffset`), `clearanceSeconds`, `scanCooldown`, and laser-beam geometry (`barrierWidth`, `laserWidth`, `laserCount`, `laserBottom`, `laserSpacing`).

### Admin & persistence

* `Config.Admin` — `ace` permission (`vlr.doorlock.admin`), framework `groups`, and `bypassLocks` (admins open any door, ignoring access rules — disable to test job-locked doors on an admin account).
* `Config.Persistence` — `persistStates`, `auditLimit`, `auditRetentionDays`.

### Presentation

* `Config.Audio` — lock/unlock beep sound set.
* `Config.Animation` — keycard-style use animation (`dictionary`, `clip`, `duration`).
* `Config.Webhook` — optional Discord audit webhook (`enabled`, `url`, `username`, `color`).

### Static doors

`Config.StaticDoors` — optional config-defined doors (same shape the in-game editor produces). They use **negative runtime IDs** and are **read-only** in the panel; database doors are recommended.

{% hint style="info" %}
The bridge also honours optional override hooks if you define them in config: `Config.GetIdentifier`, `Config.GetJob`, `Config.GetGang`, and `Config.IsAdmin`. These let you bolt the access logic onto a custom framework. They are not present by default.
{% endhint %}

## Commands

| Command         | Default                    | Description                                                                                                                                        |
| --------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/dooradmin`    | `Config.Commands.admin`    | Open the Valora Doorlock administration panel. Visible to admins, property owners and assigned editors; non-admins only see doors they may manage. |
| `/doorlock`     | `Config.Commands.toggle`   | Lock or unlock the closest door.                                                                                                                   |
| `/lockpickdoor` | `Config.Commands.lockpick` | Attempt to pick the closest eligible lock.                                                                                                         |

By default `E` toggles the closest door and `G` attempts a lockpick (configurable via `Config.Keys`).

## Developer API

### Server exports

```lua
-- Read a single door by its runtime id (full data)
local door = exports.vlr_doorlock:getDoor(1)

-- Read every door
local allDoors = exports.vlr_doorlock:getDoors()

-- Find a door by its unique internal name
local mrpd = exports.vlr_doorlock:getDoorByName('mrpd_reception')

-- Set a door's state: 0 = unlocked, 1 = locked. Returns true on success.
exports.vlr_doorlock:setState(1, 1)
```

| Export                | Side   | Description                                                        |
| --------------------- | ------ | ------------------------------------------------------------------ |
| `getDoor(id)`         | server | Returns the door object for a runtime id, or `nil`.                |
| `getDoors()`          | server | Returns the list of all doors.                                     |
| `getDoorByName(name)` | server | Returns the door whose unique internal name matches, or `nil`.     |
| `setState(id, state)` | server | Sets a door locked (`1`) / unlocked (`0`); returns `true`/`false`. |

### Client exports

| Export              | Side   | Description                                                     |
| ------------------- | ------ | --------------------------------------------------------------- |
| `useClosestDoor()`  | client | Toggle the closest eligible door (the `E` action).              |
| `pickClosestDoor()` | client | Attempt to lockpick the closest eligible door (the `G` action). |
| `getClosestDoor()`  | client | Returns the currently tracked closest-door object.              |

### Events

```lua
AddEventHandler('vlr_doorlock:stateChanged', function(source, doorId, locked, reason)
    -- source : the player who triggered it (0 for system/external)
    -- doorId : the door's runtime id
    -- locked : boolean (true = now locked)
    -- reason : string describing the trigger (e.g. 'external')
    -- Integrate alarms, dispatch, robberies or logging here.
end)
```

| Event                          | Direction     | Purpose                                                                        |
| ------------------------------ | ------------- | ------------------------------------------------------------------------------ |
| `vlr_doorlock:stateChanged`    | server (emit) | Fired whenever a door's lock state changes.                                    |
| `vlr_doorlock:server:setState` | server (net)  | Internal — accepts state changes only from the server console (`source == 0`). |

Internal client net events (`vlr_doorlock:client:setState`, `:sync`, `:upsert`, `:remove`, `:notify`, `:openAdmin`) and server `lib.callback` handlers (`vlr_doorlock:toggle`, `:admin`, `:beginLockpick`, `:breakLockpick`, `:canUse`, `:scanDetector`, `:getDoors`) drive the UI and are listed for integration awareness only — they are not a public API.

## Troubleshooting

| Symptom                                          | Fix                                                                                                                                                                            |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `/dooradmin` says access denied                  | Grant the ACE (`add_ace group.admin vlr.doorlock.admin allow`) or add your group to `Config.Admin.groups`. Property owners/editors only see their own doors.                   |
| A job/gang-locked door opens for an admin anyway | `Config.Admin.bypassLocks` is `true` (admins open any door). Set it to `false` to test access rules on an admin/god account.                                                   |
| Doors don't persist after restart                | Ensure `oxmysql` is started before `vlr_doorlock` and the tables exist; check the door's "State after restart" setting (always-locked / always-unlocked / restore-last-state). |
| Sliding / garage door won't close again          | It must be flagged **Automatic / sliding** — those use the fixed Valora HUD + `E` because the door object may have moved out of `ox_target` range.                             |
| Lockpicking does nothing                         | `Config.Lockpick.enabled` must be on, the door must allow lockpicking, and the player must carry a configured lockpick item.                                                   |
| Item not recognised in access rules / detector   | Confirm the item name; the bridge falls back across ox / qb / esx inventories and tries weapon name variants automatically.                                                    |
| Detector never grants clearance                  | The player must physically pass through the laser checkpoint, must not carry any prohibited item, and clearance is consumed on entry (re-scan if it expires).                  |

***

*Need something not covered here? Open a ticket — see* [*Support & Updates*](/getting-started/support.md)*.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.valoragaming.com/scripts/vlr_doorlock.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
