> For the complete documentation index, see [llms.txt](https://otters-developments.gitbook.io/otters-developments/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://otters-developments.gitbook.io/otters-developments/paid-resources/weapon-licensing/configuration.md).

# Configuration

The main configuration file is:

```
shared/config.lua
```

Most servers can leave the framework and integrations set to `auto`, then customise locations, licence classes, pricing, exam settings and logging.

## Framework and integrations

```lua
Cfg.Framework = 'auto' -- auto | qb | qbx | esx

Cfg.Integrations = {
    target = 'auto',
    inventory = 'auto',
    notify = 'otters_notifications'
}
```

### Framework

| Value  | Description                                |
| ------ | ------------------------------------------ |
| `auto` | Automatically detects Qbox, QBCore or ESX. |
| `qbx`  | Forces Qbox.                               |
| `qb`   | Forces QBCore.                             |
| `esx`  | Forces ESX.                                |

### Target

| Value       | Description                                                    |
| ----------- | -------------------------------------------------------------- |
| `auto`      | Automatically uses a supported target resource when available. |
| `ox_target` | Forces ox\_target.                                             |
| `qb-target` | Forces qb-target.                                              |
| `none`      | Uses the configured `[E]` fallback interaction.                |

### Inventory

The resource can automatically detect `ox_inventory`, `qb-inventory` or use the detected framework inventory handling.

### Notifications

```lua
Cfg.Notifications = {
    title = 'Weapon Licensing',
    duration = 5000,
    position = 'top-right',
}
```

`otters_notifications` is the default notification system. If it is unavailable, the resource falls back to `ox_lib` notifications.

## UI

```lua
Cfg.UI = {
    accent = '#de2bea',
    brand = 'OTTERS DEVELOPMENTS',
    title = 'Weapon Licensing',
    subtitle = 'Apply for and complete your firearm licence examination',
    closeOnDeath = true,
}
```

The main colour variables can also be changed in:

```
html/css/theme.css
```

## Multiple ped locations

Add as many licensing locations as required inside `Cfg.Peds`.

```lua
Cfg.Peds = {
    {
        enabled = true,
        model = 's_m_y_cop_01',
        coords = vec4(441.09, -978.9, 30.69, 180.22),
        scenario = 'WORLD_HUMAN_CLIPBOARD',
    },

    -- Second location example
    -- {
    --     enabled = true,
    --     model = 's_m_y_cop_01',
    --     coords = vec4(0.0, 0.0, 0.0, 0.0),
    --     scenario = 'WORLD_HUMAN_CLIPBOARD',
    -- },
}
```

Set `enabled = false` on any location you want to keep in the config without spawning it.

## Target and fallback interaction

```lua
Cfg.Target = {
    icon = 'fas fa-id-card-alt',
    label = 'Apply for Weapon Licence',
    distance = 4.0,
}

Cfg.FallbackInteraction = {
    enabled = true,
    key = 38,
    drawDistance = 10.0,
    interactDistance = 4.0,
    text = '[E] Apply for Weapon Licence',
}
```

The larger default interaction distance makes the licensing ped easier to target when they are positioned behind a desk or counter.

## Admin panel

```lua
Cfg.Admin = {
    enabled = true,
    command = 'weaponlicenseadmin',

    acePermissions = {
        'otters_weaponlicense.admin',
        'group.admin',
        'group.god',
    },

    acePermission = 'otters_weaponlicense.admin',
    addPhysicalItemOnGrant = true,
    removePhysicalItemOnRevoke = true,
}
```

Any **one** entry in `acePermissions` grants staff access. Permission checks are also performed server-side when staff refresh players, grant licences or revoke licences.

For a dedicated permission only, use:

```lua
acePermissions = {
    'otters_weaponlicense.admin',
}
```

Then add this to `server.cfg`:

```cfg
add_ace group.admin otters_weaponlicense.admin allow
```

## Examination settings

```lua
Cfg.Test = {
    requiredCorrect = 8,
    questionTimer = 30,
    shuffleQuestions = true,
    shuffleAnswers = false,
    chargeWhen = 'pass',
    sessionTimeout = 900,
}
```

| Option             | Description                                                                       |
| ------------------ | --------------------------------------------------------------------------------- |
| `requiredCorrect`  | Number of correct answers required to pass.                                       |
| `questionTimer`    | Time allowed for each question in seconds.                                        |
| `shuffleQuestions` | Randomises question order for each new test.                                      |
| `shuffleAnswers`   | Randomises the answer options when enabled.                                       |
| `chargeWhen`       | Use `pass` to charge only on success or `start` to charge before the examination. |
| `sessionTimeout`   | Maximum lifetime of an unfinished server-side exam session.                       |

## Criminal-record checks

```lua
Cfg.CriminalRecord = {
    enabled = true,
    provider = 'auto',
    noProviderAction = 'allow',

    autoPriority = {
        'ps-mdt',
        'lb-tablet',
        'metadata',
        'custom',
    },
}
```

Supported provider values are:

| Provider    | Description                                            |
| ----------- | ------------------------------------------------------ |
| `auto`      | Uses the first available provider from `autoPriority`. |
| `ps-mdt`    | Uses the PS-MDT felony check.                          |
| `lb-tablet` | Uses the configured LB Tablet SQL query.               |
| `metadata`  | Uses Qbox/QBCore criminal-record metadata.             |
| `custom`    | Uses your own SQL query.                               |
| `none`      | Disables the criminal-record check.                    |

`noProviderAction = 'allow'` keeps licensing usable if no configured provider is available. Change it to `deny` if applications should be blocked when a criminal-record provider cannot be found.

### Custom SQL example

```lua
custom = {
    enabled = true,
    query = 'SELECT 1 FROM my_criminal_records WHERE citizenid = ? LIMIT 1',
},
```

## Licence classes

Each licence is configured separately inside `Cfg.Licenses`.

```lua
weaponlicense = {
    enabled = true,
    label = 'Class 1 - Pistol Licence',
    shortLabel = 'Pistol Licence',
    description = 'Entry-level firearm licence covering legal pistol ownership and handling.',
    price = 10000,
    requirement = nil,
    item = 'weaponlicense',
},
```

To require an earlier licence, set `requirement` to its config key:

```lua
weaponlicense2 = {
    enabled = true,
    label = 'Class 2 - SMG Licence',
    shortLabel = 'SMG Licence',
    price = 100000,
    requirement = 'weaponlicense',
    item = 'weaponlicense2',
},
```

The default progression is:

```
Class 1 - Pistol Licence
        ↓
Class 2 - SMG Licence
        ↓
Class 3 - Assault Rifle Licence
```

## Questions

Questions are grouped by the matching licence key under `Cfg.Questions`.

```lua
Cfg.Questions = {
    weaponlicense = {
        {
            question = 'Where is the only legal place to carry a concealed pistol?',
            options = {
                'In my hand',
                'In a secure holster',
                'In my waistband',
                'In my backpack'
            },
            answer = 2
        },
    },
}
```

`answer` is the number of the correct option. In the example above, option `2` is correct.

When `shuffleQuestions = true`, the examination receives a fresh random question order each time. The result is calculated server-side.

## Discord logging

```lua
Cfg.Discord = {
    enabled = true,
    url = '',
    username = 'Otters Weapon Licensing',
    avatar_url = '',
    events = {
        started = true,
        passed = true,
        failed = true,
        denied = true,
        adminGranted = true,
        adminRevoked = true,
    },
}
```

Paste your Discord webhook into `url` and disable any individual event you do not want logged.

## Language

All player-facing resource messages can be changed inside:

```lua
Cfg.Lang = {
    ...
}
```

This includes application errors, criminal-record denials, test results and admin grant/revoke notifications.


---

# 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 following URL with the `ask` and `goal` query parameters:

```
GET https://otters-developments.gitbook.io/otters-developments/paid-resources/weapon-licensing/configuration.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `build a script that syncs our docs to a CMS` lets GitBook tailor the answer to that use case.

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.
