> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shdevelopment.org/llms.txt
> Use this file to discover all available pages before exploring further.

# sh-policejob: Full Law Enforcement System for RedM

> sh-policejob is a full RedM law enforcement resource for VORP and RSG with duty stations, payroll, patrols, management, cuffs, searches, robbery, detective tools, badges, supply wagons, prison wagons, shops, and witness alerts.

sh-policejob is a full-featured law enforcement resource for RedM roleplay servers. It supports VORP and RSG and lets you run police, sheriff, marshal, detective, or custom law jobs under one configurable system.

The resource includes duty stations, payroll, patrol zones, a NUI command panel, cuffs and escorting, inventory search and seizure, civilian robbery support, detective inspections, badge props, prison wagons, supply wagons, armories, stores, witness alerts, localization, and Discord logging.

## Overview

| Detail                             | Value                                             |
| ---------------------------------- | ------------------------------------------------- |
| Resource                           | `sh-policejob`                                    |
| Frameworks                         | VORP, RSG                                         |
| Required database/inventory bridge | `oxmysql` server script is loaded by the manifest |
| Localization                       | English, Spanish, German                          |
| Interaction modes                  | `prompt`, `text3d`, `target`                      |
| Optional target resource           | `ox_target`                                       |
| UI                                 | NUI panel and badge adjustment UI                 |

## Dependencies

<Tabs>
  <Tab title="VORP">
    Required:

    * `vorp_core`
    * `vorp_inventory`
    * `oxmysql`

    VORP inventory is used for usable items, player inventory searches, custom armory inventories, and police stores.
  </Tab>

  <Tab title="RSG">
    Required:

    * `rsg-core`
    * `rsg-inventory`
    * `oxmysql`

    RSG inventory is used for usable items, armory stashes, police stores, inventory searches, and supply wagon stashes.
  </Tab>

  <Tab title="Optional">
    Optional integrations:

    * `ox_target` if `Config.InteractionMode = "target"` or `Config.ThirdEye.Enabled = true`
    * `core_inventory` as a legacy fallback for some RSG inventory flows
    * Custom hooks through `Config.Integration`
  </Tab>
</Tabs>

<Warning>
  Start your framework, inventory, and `oxmysql` before `sh-policejob`. If `ox_target` is enabled, start it before this resource as well.
</Warning>

## Features

<Accordion title="Multi-Department Duty System">
  Configure multiple law jobs in `Config.PoliceJobs`, such as `police`, `sheriff`, and `marshal`. Duty stations can use markers or NPCs and can display map blips. Officers must be on duty before most job-locked actions are available.
</Accordion>

<Accordion title="Command and Keybind Controls">
  Configure command names and control hashes in `Config.Commands` and `Config.Keys`. Defaults include `/police_duty`, `/policepanel`, `/policeradial`, duty prompt key `E`, radial key `U`, and lockpick key `U`.
</Accordion>

<Accordion title="Management Panel">
  The NUI police panel shows on-duty officers, logistics, patrols, armories, stores, supply wagons, prison wagons, and officer management. Senior officers can hire, fire, promote, and demote based on `Config.Management`.
</Accordion>

<Accordion title="Payroll">
  Officers receive automatic salary payments on a configurable interval. Pay can be set per job and per grade through `Config.Payroll.Jobs`.
</Accordion>

<Accordion title="Patrol Zones and Ambient Patrols">
  Patrol zones define center points, radii, and pay multipliers. Officers can start and stop patrols from the panel or commands. Ambient patrol tasks can spawn timed checkpoints with optional rewards and timeout rules.
</Accordion>

<Accordion title="Handcuffs, Escorting, and Lockpicking">
  Officers can cuff, uncuff, escort, and transport suspects. Cuffs, keys, and lockpicks are item-driven when enabled. Cuffed state persists in `cuffed.json`.
</Accordion>

<Accordion title="Search and Seizure">
  On-duty officers can search cuffed suspects, open their inventory, and seize cash, gold, or evidence items. Protected items and allowed evidence items are configurable.
</Accordion>

<Accordion title="Civilian and NPC Robbery">
  The robbery system is separate from police search. It supports `/rob`, target state checks, player robbery inventory access, and NPC robbery rewards for dead or hogtied NPCs.
</Accordion>

<Accordion title="Detective Inspections">
  Detectives can use a `detective_kit` item or configured inspect control to inspect NPCs or players. Inspections use a timed progress flow and can return cause-of-death style information.
</Accordion>

<Accordion title="Badge System">
  Officers can equip, remove, and adjust 3D badge props. Badge presets are saved in `badge_positions.json`. Default badge types include marshal, detective, sheriff, and deputy.
</Accordion>

<Accordion title="Prison Wagons">
  Officers can spawn prison wagons, place escorted suspects inside, pull prisoners out, and return wagons. Each wagon has a configured job, grade requirement, spawn point, prisoner limit, and escape damage threshold.
</Accordion>

<Accordion title="Supply Wagons">
  Supply wagons provide personal or configured stash access for officers. RSG has a built-in stash implementation, while VORP servers can use `Config.Integration.OpenSupplyInventory`.
</Accordion>

<Accordion title="Armories and Police Stores">
  `Config.PoliceShops` creates shared armory/storage locations. `Config.PoliceStores` creates stocked police supply stores with per-item prices, amounts, metadata, and grade requirements.
</Accordion>

<Accordion title="Witness Alerts">
  Gunshots can trigger witness alerts for on-duty officers after a configurable delay. Alerts include location labels, optional blips, cooldowns, and minimum online officer checks.
</Accordion>

<Accordion title="Discord Webhooks">
  Separate webhook channels are available for duty, patrol, search, seizure, wagon, alert, inventory, and cuff events.
</Accordion>

## Installation

<Steps>
  <Step title="Add the resource">
    Place the `sh-policejob` folder in your server `resources` directory.
  </Step>

  <Step title="Install dependencies">
    Make sure your framework, inventory resource, and `oxmysql` are installed and started before `sh-policejob`.
  </Step>

  <Step title="Configure the framework">
    Open `config.lua` and set `Config.Framework` to `"vorp"` or `"rsg"`.
  </Step>

  <Step title="Install items">
    For VORP, run `install/items.sql` or merge it into your existing item table. For RSG, copy the entries from `install/rsgitems.txt` into `rsg-core/shared/items.lua`.
  </Step>

  <Step title="Add item images">
    Add item images for any new RSG inventory entries you keep. The package includes `install/images/detective_kit.png`.
  </Step>

  <Step title="Configure jobs">
    Add your law jobs to `Config.PoliceJobs` and make sure those job names exist in your framework.
  </Step>

  <Step title="Configure stations and locations">
    Update duty stations, management locations, armories, stores, patrol zones, prison wagons, and supply wagons for your map layout.
  </Step>

  <Step title="Configure logging">
    Add Discord webhook URLs in `Config.Webhooks`. Leave a webhook value blank to disable that log channel.
  </Step>

  <Step title="Start the resource">
    Add `ensure sh-policejob` to `server.cfg` after its dependencies.
  </Step>
</Steps>

```cfg server.cfg theme={null}
ensure oxmysql
ensure vorp_core
ensure vorp_inventory
# or:
# ensure rsg-core
# ensure rsg-inventory

# Optional if using target mode
# ensure ox_target

ensure sh-policejob
```

## Item Setup

The default config references these items:

| Item              | Purpose                          | Required by default                           |
| ----------------- | -------------------------------- | --------------------------------------------- |
| `detective_kit`   | Starts detective inspections     | Yes, if detective item requirement is enabled |
| `handcuffs`       | Cuff item                        | Yes, if cuff items are enabled                |
| `handcuffkey`     | Uncuff item                      | Yes, if cuff items are enabled                |
| `lockpick`        | Lockpick escape/ally uncuff item | Referenced by config, often already exists    |
| `badge_marshal`   | Marshal badge item               | Optional/default badge item                   |
| `badge_detective` | Detective badge item             | Optional/default badge item                   |
| `badge_sheriff`   | Sheriff badge item               | Optional/default badge item                   |
| `bandage`         | Default police store item        | Often already exists                          |

<Note>
  `install/items.sql` intentionally does not insert `lockpick` or `bandage` because many servers already define those items. Add them manually if your server does not have them.
</Note>

## Core Configuration

```lua config.lua theme={null}
Config.Framework = "vorp" -- "vorp" or "rsg"
Config.Debug = false
Config.Locale = "en" -- "en", "es", "de"

Config.PoliceJobs = {
    { name = "police",  label = "Lawman",  minGrade = 0 },
    { name = "sheriff", label = "Sheriff", minGrade = 0 },
    { name = "marshal", label = "Marshal", minGrade = 0 },
}

Config.Commands = {
    Duty   = "police_duty",
    Panel  = "policepanel",
    Radial = "policeradial",
}

Config.Keys = {
    dutyPrompt       = 0xCEFD9220, -- E
    Radial           = 0xD8F73058, -- U
    Search           = nil,
    detectiveInspect = nil,
    Cuff             = nil,
    Lockpick         = 0xD8F73058, -- U
}
```

## Interaction Modes

<Tabs>
  <Tab title="prompt">
    Uses native RedM prompts and is the default option.

    ```lua config.lua theme={null}
    Config.InteractionMode = "prompt"
    ```
  </Tab>

  <Tab title="text3d">
    Uses 3D text and markers for interactions.

    ```lua config.lua theme={null}
    Config.InteractionMode = "text3d"
    ```
  </Tab>

  <Tab title="target">
    Uses third-eye target interactions. Requires `ox_target`.

    ```lua config.lua theme={null}
    Config.InteractionMode = "target"

    Config.ThirdEye = {
        Enabled = true,
        Resource = "ox_target",
        UseForDuty = true,
        UseForPanel = true,
        UseForShop = true,
        UseForSupplyWagon = true,
        UseForPrisonWagon = true,
    }
    ```
  </Tab>
</Tabs>

## Payroll and Patrols

```lua config.lua theme={null}
Config.Payroll = {
    Enabled = true,
    IntervalMinutes = 15,
    CurrencyRSG = "cash",
    CurrencyVORP = 0, -- 0 = cash, 1 = gold
    Jobs = {
        police = {
            [0] = 15,
            [1] = 35,
            [2] = 55,
            [3] = 65,
            [4] = 100,
            [5] = 125,
            [6] = 190,
            [7] = 225,
            [8] = 250,
        },
    },
}

Config.PatrolBasePayPerMinute = 15

Config.PatrolAmbient = {
    Enabled = true,
    MinDelayMs = 120000,
    MaxDelayMs = 240000,
    CheckpointTimeoutMs = 300000,
    RadiusFraction = 0.6,
    CompletionRange = 10.0,
    RewardCash = 0,
    RewardGold = 0,
}
```

## Enforcement Configuration

```lua config.lua theme={null}
Config.Cuffs = {
    CuffCooldownMs = 1500,
    UseItems = true,
    CuffItem = "handcuffs",
    KeyItem = "handcuffkey",
    LockpickItem = "lockpick",
    ConsumeCuffs = false,
    ConsumeKeys = false,
    ConsumeLockpick = true,
    AllowSelfLockpick = false,
    AllowAllyLockpick = true,
    RequireOnDuty = true,
    RequireWeaponOut = false,
}

Config.Search = {
    ProtectedItems = { "id_card", "police_badge" },
    EvidenceItems = { "gold_bar", "stolen_goods", "illegal_weapon" },
    MaxSeizeCashPercent = 100,
    AllowedSearchJobs = { "police", "sheriff", "marshal" },
    maxDistance = 1.5,
    minGrade = 0,
}
```

## Robbery Configuration

<Warning>
  RSG: Disable default RSG Robbery by going to rsg-inventory\server\events, Comment out code related to "targetMeta"
</Warning>

```lua config.lua theme={null}
Config.Robbery = {
    Enabled = true,
    Command = "rob",
    DebugCommand = "robdebug",
    MaxDistance = 1.5,
    CooldownMs = 2500,

    StateCheck = {
        ResponseTimeoutMs = 2000,
        AllowHogtied = true,
        AllowDead = false,
        AllowHandsRaised = true,
    },

    NPC = {
        Enabled = true,
        LootOnce = true,
        CooldownMs = 120000,
        AllowDead = true,
        AllowHogtied = true,
        Cash = {
            Enabled = true,
            Chance = 45,
            Min = 2,
            Max = 12,
        },
    },
}
```

## Detective and Badge Configuration

```lua config.lua theme={null}
Config.Detective = {
    Enabled = true,
    Jobs = { "detective", "marshal", "sheriff", "police" },
    RequiredItem = "detective_kit",
    AllowItemUse = true,
    MaxDistance = 1.25,
    AllowNPC = true,
    AllowPlayers = true,
    InspectDurationMs = 10000,
}

Config.Badges = {
    Enabled = true,
    RequireOnDuty = true,
    ItemUseEnabled = true,
    AllowedJobs = { "marshal", "sheriff", "police", "detective" },
    SaveFile = "badge_positions.json",
    Commands = {
        Equip = "badge",
        Remove = "unbadge",
        Adjust = "adjustbadge",
    },
}
```

## Wagons, Shops, and Stores

Prison wagons, supply wagons, armories, and stores are all configured in `config.lua`.

| Config section        | Purpose                                                                                 |
| --------------------- | --------------------------------------------------------------------------------------- |
| `Config.PrisonWagons` | Prison transport wagon spawns, job locks, grade locks, max prisoners, and escape damage |
| `Config.Supply`       | Supply wagon stash settings, controls, range, and hold duration                         |
| `Config.SupplyWagons` | Supply wagon spawn locations, job locks, personal stash behavior, and return distance   |
| `Config.PoliceShops`  | Shared armory/storage locations                                                         |
| `Config.PoliceStores` | Stocked supply stores with item lists, prices, amounts, metadata, and grade locks       |

<Note>
  RSG supply wagon stashes work through `rsg-inventory` by default. VORP supply wagon stash support is intentionally exposed through `Config.Integration.OpenSupplyInventory` so you can connect your server's stash implementation.
</Note>

## Witness Alerts

```lua config.lua theme={null}
Config.Witness = {
    Enabled = true,
    Radius = 30.0,
    TimeToNotify = 15,
    IgnorePoliceShots = true,
    CooldownSeconds = 30,
    MinPoliceOnline = 1,

    CreateBlip = true,
    BlipDuration = 300,
    BlipLabel = "Shots Fired",
    BlipRadius = 60.0,
    ZoneFallback = "Wilderness",
}
```

## Webhooks

```lua config.lua theme={null}
Config.Webhooks = {
    Duty = "",
    Patrol = "",
    Search = "",
    Seizure = "",
    Wagon = "",
    Alerts = "",
    Inventory = "",
    Cuffs = "",
}

Config.WebhookUsername = "SH POLICE JOB"
```

## Commands

| Command                 | Default                            | Purpose                                               |
| ----------------------- | ---------------------------------- | ----------------------------------------------------- |
| Duty                    | `/police_duty`                     | Toggle duty state from command                        |
| Panel                   | `/policepanel`                     | Open the police management panel                      |
| Radial                  | `/policeradial`                    | Open the police radial menu                           |
| Badge equip             | `/badge [type]`                    | Equip a configured badge type                         |
| Badge remove            | `/unbadge`                         | Remove the equipped badge                             |
| Badge adjust            | `/adjustbadge`                     | Open badge adjustment UI                              |
| Robbery                 | `/rob`                             | Start civilian player/NPC robbery flow                |
| Robbery debug           | `/robdebug`                        | Debug nearby robbery target state                     |
| Patrol start            | `/pd_patrol_start [zoneId]`        | Start a patrol zone                                   |
| Patrol stop             | `/pd_patrol_stop`                  | Stop active patrol                                    |
| Seize cash              | `/pd_takecash [amount]`            | Take cash during an active search session             |
| Seize gold              | `/pd_takegold [amount]`            | Take gold during an active search session             |
| Seize item              | `/pd_takeitem [itemName] [amount]` | Take an evidence item during an active search session |
| Prison wagon spawn      | `/pd_wagon_spawn [wagonId]`        | Spawn a configured prison wagon                       |
| Prison wagon put escort | `/pd_wagon_putescort`              | Place escorted suspect in wagon                       |
| Prison wagon put        | `/pd_wagon_put [targetId]`         | Place target in wagon                                 |
| Prison wagon pull       | `/pd_wagon_pull [targetId]`        | Pull target from wagon                                |

<Note>
  Some commands are configurable through `Config.Commands`; utility/debug commands are registered directly in their feature files.
</Note>

## Server Exports

Use these exports from other server resources.

```lua theme={null}
-- Duty state
local isOnDuty = exports["sh-policejob"]:IsPlayerOnDuty(source)
local dutyData = exports["sh-policejob"]:GetPlayerDutyData(source)

-- Counts and lists
local total = exports["sh-policejob"]:GetOnDutyCount()
local seniorSheriffs = exports["sh-policejob"]:GetOnDutyCount(5, "sheriff")
local officers = exports["sh-policejob"]:GetOnDutyList()
local officersAlias = exports["sh-policejob"]:GetOnDutyOfficers()
local officersAlias2 = exports["sh-policejob"]:onDutyOfficers()
local marshals = exports["sh-policejob"]:GetOnDutyOfficersByJob("marshal")
local dutyMap = exports["sh-policejob"]:GetOnDutyMap()

-- Cuffs
local cuffed = exports["sh-policejob"]:IsPlayerCuffed(targetId)

-- Alerts
exports["sh-policejob"]:CreateExternalAlert("custom", {
    title = "Bank alarm",
    description = "A bank alarm was triggered.",
    coords = vector3(-276.85, 805.76, 119.38),
})

-- Inventory helpers
exports["sh-policejob"]:OpenPlayerSearchInventory(officerSrc, targetSrc, inventoryCfg)
exports["sh-policejob"]:OpenOtherInventory(officerSrc, targetSrc)
exports["sh-policejob"]:OpenSupplyStash(ownerSrc, stashId, label)

-- Patrol
local sessions = exports["sh-policejob"]:GetPatrolSessions()
```

### Duty Export Return Shapes

```lua theme={null}
-- GetPlayerDutyData(src)
{
    job = "marshal",
    grade = 4,
    stationId = "valentine",
    startedAt = 1717000000,
}

-- GetOnDutyList(minGrade, jobName)
{
    {
        source = 1,
        name = "Officer Name",
        job = "marshal",
        grade = 4,
        stationId = "valentine",
        startedAt = 1717000000,
    },
}
```

<Note>
  Return types differ by export. `IsPlayerOnDuty` returns a boolean, `GetOnDutyCount` returns a number, `GetPlayerDutyData` returns a table or `nil`, and list/map exports return tables.
</Note>

## Client Exports

Use these exports from client resources when you need badge or notification helpers.

```lua theme={null}
exports["sh-policejob"]:EquipBadge("marshal")
exports["sh-policejob"]:RemoveBadge()

local badge = exports["sh-policejob"]:GetEquippedBadge()

exports["sh-policejob"]:PushPoliceNotification("Message", "success", 5000)
```

## Integration Hooks

You can override or provide these hooks in `config.lua`.

```lua theme={null}
Config.Notification = {
    ClientEvent = nil, -- example: "RSGCore:Notify"
    ServerEvent = nil,
}

Config.Integration.HasItem = function(src, itemName, amount)
    return true
end

Config.Integration.OpenSupplyInventory = function(src, stashId, label)
    -- Connect your VORP/custom stash system here.
end

Config.Integration.OpenPoliceShop = function(src, shopCfg)
    -- Connect a custom armory/shop script here.
end

Config.Integration.OpenPoliceStore = function(src, storeCfg, itemsForGrade)
    -- Connect a custom store script here.
end
```

## Persistent Files

| File                   | Purpose                              |
| ---------------------- | ------------------------------------ |
| `cuffed.json`          | Stores persisted cuffed player state |
| `badge_positions.json` | Stores saved badge placement presets |

<Warning>
  Back up `cuffed.json` and `badge_positions.json` before deleting or reinstalling the resource if you need to preserve active cuff states or saved badge positions.
</Warning>

## Compatibility Notes

* Make sure the resource folder is named `sh-policejob` if you use the export examples exactly as written.
* If you run `sh-policemdt`, keep the same framework setting in both resources so duty exports and character lookups resolve correctly.
* The VORP supply wagon stash path is a hook point by default. Configure `Config.Integration.OpenSupplyInventory` if your server needs VORP supply wagon storage.
* If player inventory search fails on VORP, confirm your `vorp_inventory` build supports `openPlayerInventory`.
* If RSG armories or stores fail to open, confirm `rsg-inventory` exposes `OpenInventory`, `CreateShop`, and `OpenShop`.
