> For the complete documentation index, see [llms.txt](https://code-after-sex.gitbook.io/script-documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://code-after-sex.gitbook.io/script-documentation/cas-police/developers.md).

# Developers

CAS Advanced Police & Law System developer reference: server and client exports, events, radial actions, state bags and Discord webhooks for other VORP and RSG scripts.

[Back to the guide](/script-documentation/cas-police/cas-advanced-police-and-law-system.md)

Other scripts can ask cas-police about a player and act on him through exports, listen to what happens through events, and read a few state bags. The names start with the resource name, so keep the folder name `cas-police`.

Every export is called as `exports['cas-police']:...`:

```lua
if exports['cas-police']:isOnDutyLawman(playerId) then
    -- another script can react to a lawman on duty
end

if exports['cas-police']:isCuffed(targetId) or exports['cas-police']:isHogtied(targetId) then
    -- another script can see a restrained player
end
```

## Server exports

`src` is a player's server id. `office` is an office key from `Config.Offices` (for example `'marshal'`).

| Export                                            | Returns                        | What it does                                                                                                                                                                                           |
| ------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `isOnDutyLawman(src)`                             | `boolean`                      | A lawman who is on duty right now                                                                                                                                                                      |
| `isLawman(src)`                                   | `boolean`                      | A lawman, on duty or not                                                                                                                                                                               |
| `getLawOnDuty(office?)`                           | `number`                       | Lawmen on duty, in one office or (no argument) in all                                                                                                                                                  |
| `isCuffed(src)`                                   | `boolean`                      | He is in cuffs                                                                                                                                                                                         |
| `isHogtied(src)`                                  | `boolean`                      | He is hogtied                                                                                                                                                                                          |
| `cuff(src)`                                       | `boolean`                      | Cuffs him at once, with no arrest sequence and no officer. `false` if he is offline or already cuffed.                                                                                                 |
| `uncuff(src)`                                     | `boolean`                      | Takes his cuffs off. `false` if he was not cuffed.                                                                                                                                                     |
| `hogtie(src)`                                     | `boolean`                      | Knocks him down hogtied. `true` once the order is sent.                                                                                                                                                |
| `jail(src, minutes, jailKey?, reason?)`           | `ok, errKey`                   | Sends him to jail. Minutes are kept between `minMinutes` and `maxMinutes`. No `jailKey` = `Config.Jail.primary`. `errKey` is `'err_jailed'` (already jailed), `'err_not_online'` or `'err_no_target'`. |
| `release(src)`                                    | `boolean`                      | Lets him out early and moves him to the release point (logged like `/unjail`)                                                                                                                          |
| `isJailed(src)`                                   | `jailed, secondsLeft, jailKey` | Whether he is serving time, how long is left, and where                                                                                                                                                |
| `getWarrants()`                                   | `table`                        | Every open warrant and bounty (see below)                                                                                                                                                              |
| `getLedger(office)`                               | `number`                       | The balance of the office ledger                                                                                                                                                                       |
| `addLedger(office, amount, note?)`                | `boolean`                      | Pays money into the ledger. The entry is signed with your resource name.                                                                                                                               |
| `removeLedger(office, amount, note?)`             | `boolean`                      | Takes money out. `false` when the ledger cannot cover it and `emptyBehavior = 'stop'`.                                                                                                                 |
| `alertLaw({ kind, text, coords, from?, place? })` | alert id or `false`            | Sends an alert to the lawmen on duty, with no cooldown                                                                                                                                                 |

The ledger exports follow `Config.Society`: with `ledgerMode = 'shared'` every office key reaches the one shared ledger, and with `mode = 'syn'` they read and write the syn\_society ledger.

For `alertLaw`, `kind` should be one of `Config.Alerts.kinds` (anything else becomes `'other'`), `text` is cut to `maxLength`, and `coords` is a `vector3` (or `nil` for no blip). `from` defaults to "A citizen" and `place` to the nearest town (or "Out in the wild").

Each row of `getWarrants()`:

| Field                                         | What it is                                               |
| --------------------------------------------- | -------------------------------------------------------- |
| `id`                                          | Warrant number                                           |
| `offender`, `offenderCid`                     | Name and character id                                    |
| `status`                                      | `'warrant'` or `'bounty'`                                |
| `fugitive`                                    | `true` for a warrant set by the jail after an escape     |
| `charges`                                     | List of `{ id, label, amount, jailtime, category, ... }` |
| `reportId`                                    | The report it came from, or `nil`                        |
| `issuedAt`, `bountyAt`                        | Unix times                                               |
| `issuedBy`, `office`                          | Who issued it, and the office label                      |
| `claimedBy`, `claimedCid`, `claimedAt`        | The hunter who claimed it, if any                        |
| `reward`, `rewardBase`, `rewardDaily`, `days` | What it pays now, and how that is made up                |
| `mugshot`                                     | The photograph link of the citizen, or `nil`             |

### Examples

A robbery that needs two lawmen on duty, and tells them when it starts:

```lua
RegisterNetEvent('myrobbery:start', function()
    local src = source
    if exports['cas-police']:getLawOnDuty() < 2 then
        return -- not enough law in the county
    end
    exports['cas-police']:alertLaw({
        kind   = 'robbery',
        text   = 'The Valentine bank is being robbed',
        coords = GetEntityCoords(GetPlayerPed(src)),
    })
end)
```

A shop that refuses a cuffed customer:

```lua
if exports['cas-police']:isCuffed(src) then
    return -- hands in irons, no trade
end
```

Paying a seized sum into the office ledger, and sending a man to the island:

```lua
exports['cas-police']:addLedger('marshal', 250, 'Seized from a moonshine still')

local ok, err = exports['cas-police']:jail(targetId, 20, 'sisika', 'Moonshining')
if not ok then print('could not jail: ' .. tostring(err)) end
```

## Client exports

| Export        | Returns                                  |
| ------------- | ---------------------------------------- |
| `isCuffed()`  | The local player is cuffed               |
| `isHogtied()` | The local player is hogtied              |
| `isJailed()`  | The local player is in jail              |
| `isDragged()` | The local player is being led by the arm |
| `isOnDuty()`  | The local player is a lawman on duty     |
| `isLawman()`  | The local player is a lawman             |

```lua
-- client: no emotes while cuffed
if exports['cas-police']:isCuffed() then return end
```

## Server events

These are local server events (fired with `TriggerEvent`, not from clients). Listen with `AddEventHandler`.

| Event                             | Arguments                                 | When                                                                                  |
| --------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------- |
| `cas-police:server:started`       | none                                      | The script finished starting                                                          |
| `cas-police:server:loaded`        | `src`                                     | A player's character is loaded and known to cas-police                                |
| `cas-police:server:duty`          | `src, on`                                 | A lawman went on (`true`) or off (`false`) duty                                       |
| `cas-police:server:cuffed`        | `src, by`                                 | He was cuffed. `by` is the officer's server id, or `0` (export, rejoin).              |
| `cas-police:server:uncuffed`      | `src, by`                                 | His cuffs came off                                                                    |
| `cas-police:server:hogtied`       | `src, captor`                             | He was hogtied. `captor` is a server id, or `nil`.                                    |
| `cas-police:server:unhogtied`     | `src`                                     | He is no longer hogtied                                                               |
| `cas-police:server:searchTook`    | `searcher, target, item, count`           | An item was taken in a search                                                         |
| `cas-police:server:fined`         | `officer, target, amount, reason`         | A fine was paid                                                                       |
| `cas-police:server:reportFiled`   | `reportId, officerSrc, offenderCid`       | A report was filed                                                                    |
| `cas-police:server:warrantIssued` | `warrantId, offenderCid`                  | A warrant was issued                                                                  |
| `cas-police:server:bounty`        | `warrantId`                               | A warrant turned into a bounty                                                        |
| `cas-police:server:warrantClosed` | `warrantId, status`                       | A warrant was closed: `'served'` or `'cleared'`                                       |
| `cas-police:server:turnin`        | `warrantId, hunterSrc, lawmanSrc, reward` | A hunter turned a prisoner in and was paid                                            |
| `cas-police:server:jailed`        | `src, minutes, jailKey`                   | He was sent to jail                                                                   |
| `cas-police:server:released`      | `src, how`                                | He left jail: `'served'`, `'unjail'` or `'escaped'`                                   |
| `cas-police:server:escape`        | `src, stage`                              | An escape: `'committed'` (it stands), `'failed'` (he died) or `'success'`             |
| `cas-police:server:alert`         | `alert`                                   | An alert went out: `{ id, kind, text, coords, place, from, at, office, system, cid }` |

```lua
AddEventHandler('cas-police:server:jailed', function(src, minutes, jailKey)
    print(('%s was jailed for %d minutes in %s'):format(GetPlayerName(src), minutes, jailKey))
end)
```

## Client events

| Event                     | Arguments | When                                 |
| ------------------------- | --------- | ------------------------------------ |
| `cas-police:client:ready` | none      | The script is ready on this client   |
| `cas-police:client:duty`  | `on`      | The local lawman went on or off duty |

## Client actions (`cas-police:radial`)

Every slot of the cas-police radial wheel fires the local client event `cas-police:radial` with an action name. Your own client scripts (another menu, a key, a target option) can fire the same actions:

```lua
-- client
TriggerEvent('cas-police:radial', 'book')
```

| Action        | What it does                                                                          |
| ------------- | ------------------------------------------------------------------------------------- |
| `duty`        | Go on or off duty (on duty only at a station of your office)                          |
| `book`        | Open the records book                                                                 |
| `board`       | Open the hunter's board                                                               |
| `desk`        | Open the marshal's desk                                                               |
| `locker`      | Open the office locker                                                                |
| `garage`      | Open the office garage (outdoors near an office)                                      |
| `cuff`        | Cuff or uncuff the person in front of you (like F6)                                   |
| `escort`      | Take hold of, or let go of, a cuffed person (like L)                                  |
| `chain`       | Put the nearest cuffed or hogtied man in leg irons, or take them off (like holding G) |
| `search`      | Search the nearest cuffed, hogtied or downed player                                   |
| `bookin`      | Open the booking sheet for the cuffed, hogtied or downed man next to you              |
| `jailtime`    | Show your own time left, or the nearest prisoner's within 15 m                        |
| `badge`       | Pin or take off the badge                                                             |
| `badgeadjust` | Open the badge adjust screen                                                          |
| `dispatch`    | Open the dispatch radio                                                               |
| `ride`        | Ride to the alert on the card                                                         |
| `clear`       | Clear the alert card                                                                  |
| `alert`       | Open the form to send for the law                                                     |

Each action goes through the same checks as its key or command, so a player who may not do it is told why. It is a local event: only scripts on the same client can fire it, never the server or another player.

cas-police fills CAS Radial Menu through its exports `AddItem` and `RemoveItem`, and holds it shut with `SetEnabled` while a cas-police screen is open or the player is bound (`Config.Radial`).

## State bags

cas-police sets these on each player. Read them, never write them.

| State bag       | Value                                                                                                                                 |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `isPoliceDuty`  | `true` while a lawman is on duty, else `nil`. Billing, robbery and shop scripts that worked with the stock law scripts read this one. |
| `casPoliceDuty` | `true` or `false`                                                                                                                     |
| `casPolice`     | `{ law, hunter, office, officeLabel, rank, boss, duty, admin, cid, name }`                                                            |
| `casCuffed`     | `{ by, name, office, pickable }` while cuffed, else `nil`                                                                             |
| `casHogtied`    | `{ lasso, captor }` while hogtied, else `nil` (`lasso` is `'regular'` or `'reinforced'`)                                              |
| `casDragged`    | The server id of the man leading him, else `nil`                                                                                      |
| `casJail`       | `{ jail }` while jailed, else `nil`                                                                                                   |
| `casTransport`  | `true` while a transport guard is taking him to jail                                                                                  |

```lua
-- server
if Player(src).state.isPoliceDuty then
    -- a lawman on duty
end
```

On RSG, cas-police also sets the rsg-core metadata `ishandcuffed` (true or false) and `injail` (minutes left), sets the rsg-core job duty, and answers the callback `rsg-lawman:server:getlaw` with the number of lawmen on duty.

## Debugging

Set `Config.Debug = true` to print every call a client makes to the server, as `[cas-police] rpc <name> from <id>`. It shows which screen or key reached the server and from whom. Turn it off again on a live server, because it prints a lot.

## Discord webhooks

Set them in `Config.Webhooks`. Each action has a switch (`enabled`) and a URL (`url`). An action with no URL of its own posts to `master` (the two public actions try `Config.Warrants.publicFeed.url` first). An action with no URL and no master posts nothing.

| Action           | Default | Posts when                                                                               |
| ---------------- | ------- | ---------------------------------------------------------------------------------------- |
| `reportFiled`    | on      | A report is filed (lawman, offender, charges, total, place)                              |
| `warrantIssued`  | on      | A warrant is issued                                                                      |
| `warrantCleared` | on      | A warrant is served or cleared                                                           |
| `manualJail`     | on      | A prisoner is jailed (by command, the booking sheet, transport or export)                |
| `manualUnjail`   | on      | A prisoner is released early                                                             |
| `fineIssued`     | on      | A fine is paid                                                                           |
| `searchRemoval`  | on      | An item is taken in a search (with `Config.Search.logRemovals`)                          |
| `bountyTurnin`   | on      | A hunter turns a prisoner in (hunter, offender, lawman, payout)                          |
| `alertLaw`       | on      | Any alert goes out                                                                       |
| `dispatch`       | off     | A dispatch radio message                                                                 |
| `society`        | on      | Swear in, dismiss, rank and pay changes, deposits, withdrawals, the pay roll, the locker |
| `garage`         | on      | A law car is taken out of the office garage or put away                                  |
| `hanging`        | on      | A public hanging (lawman, offender, town, charges), and a rope shot through              |
| `escape`         | on      | An escape stands                                                                         |
| `penalCode`      | on      | The penal code is changed in the book                                                    |
| `publicWarrant`  | on      | The public wanted notice of a warrant                                                    |
| `publicBounty`   | on      | The public notice of a bounty                                                            |

The log posts use a detailed embed: a title, the fields, `Config.Branding.systemName` in the footer and a time stamp, in `Config.Branding.feedColor`.

### The public warrant and bounty feed

With `Config.Warrants.publicFeed.enabled = true`, every warrant posts an in-character wanted notice through the `publicWarrant` action, and every bounty through `publicBounty`. Each one posts to its own action URL, else to `Config.Warrants.publicFeed.url`, else to `master`. The simplest setup is one public channel webhook in `publicFeed.url`.

| Part      | What it shows                                                                             |
| --------- | ----------------------------------------------------------------------------------------- |
| Title     | `WANTED: John Marston`                                                                    |
| Thumbnail | The citizen's photograph, when his file has one                                           |
| Fields    | Charges, Reward (or Bounty) amount, Office, Posted (the date with `Config.Book.gameYear`) |
| Footer    | The office label, with `Config.Branding.feedLogo` as the icon                             |
| Colour    | The office's `color`, or `Config.Branding.feedColor`                                      |

The notices stay in step with the warrants:

* When a warrant turns into a bounty, its warrant notice is deleted and a bounty notice is posted.
* When a warrant is served or cleared, its notice is deleted. The channel only ever shows active warrants.

Deleting needs the same webhook that posted, so do not change those two URLs while warrants are open.

### The staff log

`Config.Warrants.staffLog` (`enabled`, `url`) posts a plain line for every warrant issued, served and cleared. These lines are never deleted, so the history stays.
