> For the complete documentation index, see [llms.txt](https://docs.rainmad.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rainmad.com/resources/jobs-v-legal-multiplayer-job-pack/exports-and-hooks.md).

# Exports & Hooks

Jobs V exposes server-side events and exports so you can hook your own systems into the job loop — reward multipliers, custom logging, achievement trackers, whitelisting, and so on.

{% hint style="info" %}
All events and exports below are **server-side only**. The event prefix is the resource folder name — if you renamed the resource, replace `rm_jobsv` with your folder name.
{% endhint %}

### Events

Register these with a standard `AddEventHandler` in any server file of your own resource.

#### `rm_jobsv:jobStarted`

Fires when a player clocks in. In a crew, it fires once **per member**.

| Parameter | Type     | Description                         |
| --------- | -------- | ----------------------------------- |
| `source`  | `number` | Server ID of the player             |
| `jobId`   | `string` | Job identifier, e.g. `"lumberjack"` |

```lua
AddEventHandler("rm_jobsv:jobStarted", function(source, jobId)
    print(("%s started the %s job"):format(GetPlayerName(source), jobId))
end)
```

#### `rm_jobsv:jobStopped`

Fires when a player clocks out — whether the shift was completed, cancelled, or ended by disconnect. Also fires once per crew member.

| Parameter | Type     | Description             |
| --------- | -------- | ----------------------- |
| `source`  | `number` | Server ID of the player |
| `jobId`   | `string` | Job identifier          |

```lua
AddEventHandler("rm_jobsv:jobStopped", function(source, jobId)
    activeWorkers[source] = nil
end)
```

#### `rm_jobsv:shiftCompleted`

Fires when a shift is paid out, **after** the money and EXP have been given. Fires once per crew member with that member's own share.

| Parameter | Type      | Description                                                           |
| --------- | --------- | --------------------------------------------------------------------- |
| `source`  | `number`  | Server ID of the player                                               |
| `jobId`   | `string`  | Job identifier                                                        |
| `payout`  | `number`  | Money paid to this player, including level, prestige and time bonuses |
| `exp`     | `number`  | EXP awarded — `0` if no work was done                                 |
| `worked`  | `boolean` | `false` if the player clocked out without completing any task         |

```lua
AddEventHandler("rm_jobsv:shiftCompleted", function(source, jobId, payout, exp, worked)
    if not worked then return end
    exports["my_resource"]:addReputation(source, jobId, 1)
end)
```

{% hint style="warning" %}
A player can end a shift without doing anything. Always check `worked` before granting extra rewards, otherwise players can farm your reward by starting and stopping the job repeatedly.
{% endhint %}

#### `rm_jobsv:taskProgressed`

Fires every time the player advances their daily task.

| Parameter  | Type     | Description                            |
| ---------- | -------- | -------------------------------------- |
| `source`   | `number` | Server ID of the player                |
| `taskId`   | `string` | Daily task identifier                  |
| `progress` | `number` | Current progress after this increment  |
| `target`   | `number` | Progress required to complete the task |

```lua
AddEventHandler("rm_jobsv:taskProgressed", function(source, taskId, progress, target)
    if progress >= target then
        print(("%s finished daily task %s"):format(GetPlayerName(source), taskId))
    end
end)
```

### Exports

#### `getPlayerExp`

Returns the player's **total account EXP** across all jobs.

```lua
local exp = exports["rm_jobsv"]:getPlayerExp(source)
```

**Returns:** `number` — `0` if the player has no progress yet.

#### `getPlayerLevel`

Returns the player's **account level**, calculated from total EXP and capped at `Config.general.level.maxLevel`.

```lua
local level = exports["rm_jobsv"]:getPlayerLevel(source)

if level < 10 then
    -- deny access to a level-gated feature
end
```

**Returns:** `number` — `1` if the player has no progress yet.

#### `getPlayerJobExp`

Returns the player's progress **for a single job**.

```lua
local data = exports["rm_jobsv"]:getPlayerJobExp(source, "miner")
print(data.exp, data.level)
```

| Field   | Type     | Description                                                 |
| ------- | -------- | ----------------------------------------------------------- |
| `exp`   | `number` | EXP earned in this job                                      |
| `level` | `number` | Per-job level, capped at `Config.general.jobLevel.maxLevel` |

**Returns:** `table` — `{ exp = 0, level = 1 }` if the player has never worked that job.

### Example: job-level whitelist

Combining an export with an event to gate a door behind mining experience:

```lua
RegisterNetEvent("myscript:openMineDoor", function()
    local data = exports["rm_jobsv"]:getPlayerJobExp(source, "miner")

    if data.level < 5 then
        TriggerClientEvent("myscript:notify", source, "You need Miner level 5.")
        return
    end

    TriggerClientEvent("myscript:doorOpened", source)
end)
```
