> For the complete documentation index, see [llms.txt](https://jesus-gimenez.gitbook.io/xex-scripts/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jesus-gimenez.gitbook.io/xex-scripts/premium-mods/pilot-job.md).

# Pilot Job

***

<figure><img src="/files/T31D8VcLxhNWVeR9x0Jj" alt=""><figcaption></figcaption></figure>

## Overview

XeX PilotJob is a premium pilot mission system for FiveM servers. Players can accept passenger transport and rescue missions from a mission point, fly through checkpoint routes, pick up/drop off NPC passengers, and earn randomized rewards upon completion. Rescue missions add a time limit mechanic. Supports rented or player-owned vehicles, job and license restrictions, server-side cooldowns, and full webhook logging.

### Supported Frameworks

| Framework  | Version | Status                            |
| ---------- | ------- | --------------------------------- |
| ESX Legacy | 1.6.0+  | ✅ Full Support                    |
| QBCore     | Latest  | ✅ Full Support                    |
| QBox (QBx) | Latest  | ✅ Full Support (requires ox\_lib) |

***

## Features Summary

| Category     | Feature             | Description                                  |
| ------------ | ------------------- | -------------------------------------------- |
| **Missions** | Passenger Transport | Fly passengers between checkpoints           |
| **Missions** | Rescue Missions     | Timed medical evacuation flights             |
| **Missions** | Multiple Routes     | Multiple mission definitions per type        |
| **Missions** | Checkpoint System   | Sequential waypoint navigation               |
| **Missions** | Action Functions    | Custom code at each checkpoint               |
| **Vehicles** | Rental Mode         | Spawn configured vehicle at mission start    |
| **Vehicles** | Owned Vehicle Mode  | Use player's personal aircraft from garage   |
| **Vehicles** | Vehicle Color       | Customizable RGB color for spawned vehicles  |
| **Vehicles** | Fuel Level          | Configurable starting fuel                   |
| **NPCs**     | Passenger Boarding  | NPC peds board/exit aircraft dynamically     |
| **NPCs**     | Shuffled Models     | Random passenger appearances each mission    |
| **NPCs**     | Injured Ped         | Special injured NPC for rescue missions      |
| **Timer**    | Countdown           | Rescue missions with configurable time limit |
| **Timer**    | On-Screen Display   | Live remaining time shown via text           |
| **Timer**    | Mission Fail        | Auto-cancel on time expiry                   |
| **Payment**  | Startup Fee         | Configurable tax/fee to begin missions       |
| **Payment**  | Random Rewards      | Server-calculated reward between min/max     |
| **Payment**  | Cash or Bank        | Configurable payment destination             |
| **Access**   | Job Restriction     | Restrict to specific jobs                    |
| **Access**   | License Requirement | Require aircraft + optional extra license    |
| **Access**   | Server Cooldown     | Server-tracked per-player cooldown           |
| **Map**      | Map Blip            | Configurable blip at mission point           |
| **Map**      | 3D Markers          | Interaction markers at mission point         |
| **Map**      | Checkpoint Markers  | Large checkpoint markers during missions     |
| **Interact** | ox\_target          | Optional ox\_target model interaction        |
| **Interact** | Marker + E          | Traditional proximity prompt                 |
| **Webhook**  | Discord Logging     | Log mission completions with player info     |
| **System**   | Auto Updater        | JSON-based version checker                   |
| **I18n**     | 2 Languages         | English, Spanish                             |

***

## Installation

### Requirements

* FiveM Server Build 5181+
* Framework: ESX Legacy, QBCore, or QBox
* Optional: [ox\_target](https://github.com/overextended/ox_target) (for target mode)
* Optional: [ox\_lib](https://github.com/overextended/ox_lib) (required for QBox)

### Dependencies by Framework

| Framework | Required           | Optional   |
| --------- | ------------------ | ---------- |
| ESX       | es\_extended       | ox\_target |
| QBCore    | qb-core            | ox\_target |
| QBox      | qbx\_core, ox\_lib | ox\_target |

### Quick Start

{% stepper %}
{% step %}
Place 'xex\_pilotjob' in your resources folder
{% endstep %}

{% step %}
Add to server.cfg:

```
ensure xex_pilotjob
```

{% endstep %}

{% step %}
Set Config.Framework to your framework ('esx', 'qb', or 'qbox')
{% endstep %}

{% step %}
Configure missions, coordinates, and vehicles
{% endstep %}

{% step %}
Restart server
{% endstep %}
{% endstepper %}

> Note: If using QBox, ensure `ox_lib` is started before this resource.

***

## Configuration

### General Settings

```lua
Config.Language = 'en'                -- 'en' | 'es'
Config.CheckForUpdates = true         -- Auto version check on start
```

### Framework Settings

```lua
Config.Framework = 'auto'             -- 'auto' | 'esx' | 'qb' | 'qbox'
```

| Value    | Behavior                          |
| -------- | --------------------------------- |
| `'auto'` | Auto-detects: QBox → QBCore → ESX |
| `'esx'`  | Force ESX Legacy                  |
| `'qb'`   | Force QBCore                      |
| `'qbox'` | Force QBox (requires ox\_lib)     |

### Job & License Requirements

```lua
Config.JobRestriction = {
    enabled = false,                  -- Enable job restriction
    jobs = { 'pilot', 'airline' },    -- Allowed job names
}

Config.LicenseRequired = false        -- Require a license to fly
Config.LicenseNeeded = 'aircraft'     -- Primary license type ('' to disable)
Config.ExtraLicenseNeeded = 'helicopter'  -- Alternative license ('' to disable)
```

| Option                   | Default               | Description                            |
| ------------------------ | --------------------- | -------------------------------------- |
| `JobRestriction.enabled` | `false`               | Only listed jobs can start missions    |
| `JobRestriction.jobs`    | `{'pilot','airline'}` | Allowed job names when enabled         |
| `LicenseRequired`        | `false`               | Gate missions behind license check     |
| `LicenseNeeded`          | `'aircraft'`          | Primary flying license type            |
| `ExtraLicenseNeeded`     | `'helicopter'`        | Secondary license, either one suffices |

> License Logic: If `LicenseRequired = true`, the player must have EITHER `LicenseNeeded` OR `ExtraLicenseNeeded`. If both are set to `''`, no check is performed even when enabled.

### Payment Settings

```lua
Config.PaymentType = 'cash'           -- 'cash' | 'bank'
```

| Value    | Behavior                                       |
| -------- | ---------------------------------------------- |
| `'cash'` | Tax fee deducted from and rewards paid to cash |
| `'bank'` | Tax fee deducted from and rewards paid to bank |

### Interaction Settings

```lua
Config.UseOxTarget = false            -- Use ox_target instead of marker + E
Config.InteractionDistance = 1.5      -- Prompt distance (marker mode)
Config.DistanceToCheck = 50           -- Marker render distance
```

### Vehicle Settings

```lua
Config.RequireOwnedVehicles = false   -- true: use personal vehicle | false: rent
Config.OwnedVehiclesMenuPoint = vector3(-1636.72, -3115.06, 13.53)
Config.VehicleSpawnPoint = vector4(-1651.68, -3142.30, 13.99, 329.43)
Config.TeleportBackCoords = vector3(-1622.25, -3152.20, 12.29)

Config.VehicleModels = {
    passengers = 'nimbus',            -- Plane for passenger missions
    rescue = 'supervolito',           -- Helicopter for rescue missions
}

Config.VehicleColor = { r = 255, g = 255, b = 255 }  -- RGB vehicle color
Config.FuelLevel = 100.0              -- Starting fuel (0.0 - 100.0)
```

| Option                     | Default         | Description                                    |
| -------------------------- | --------------- | ---------------------------------------------- |
| `RequireOwnedVehicles`     | `false`         | Player must bring own aircraft                 |
| `OwnedVehiclesMenuPoint`   | LSIA coords     | Where the menu appears for owned mode          |
| `VehicleSpawnPoint`        | LSIA runway     | Where rented vehicles spawn                    |
| `TeleportBackCoords`       | LSIA terminal   | Where player teleports after returning vehicle |
| `VehicleModels.passengers` | `'nimbus'`      | Model for passenger transport                  |
| `VehicleModels.rescue`     | `'supervolito'` | Model for rescue missions                      |
| `VehicleColor`             | White           | Applied to rented vehicles                     |
| `FuelLevel`                | `100.0`         | Starting fuel percentage                       |

### Passengers & Peds

```lua
Config.PassengersNumber = 3           -- NPCs per transport mission

Config.PassengerModels = {
    'a_f_y_bevhills_01',
    'a_m_y_busicas_01',
    'a_m_y_stlat_01',
    'cs_jimmyboston',
    'cs_stevehains',
    'a_m_y_beach_01',
    'a_m_y_polynesian_01',
    'cs_jewelass',
    'csb_anita',
    'csb_customer',
}
```

Passenger models are shuffled at mission start, so NPCs appear randomly from the pool each time.

### Map Elements

```lua
Config.BlipData = {
    enabled = true,
    pos = vector3(-1622.25, -3152.20, 14.09),
    sprite = 572,                     -- Helicopter blip
    scale = 1.0,
    colour = 8,                       -- Blue
}

Config.MarkerData = {
    type = 2,                         -- Chevron marker
    size = { x = 0.3, y = 0.2, z = 0.15 },
    colour = { r = 96, g = 196, b = 232, a = 100 },
}
```

### Checkpoint Markers

During missions, large checkpoint markers guide the player through waypoints:

```lua
Config.CheckpointMarker = {
    type = 1,                         -- Cylinder marker
    size = vector3(7.5, 7.5, 4.5),
    colour = { r = 102, g = 204, b = 102, a = 100 },
    drawDistance = 250.0,             -- Visible from this distance
    triggerDistance = 7.0,            -- Trigger when within this distance
}
```

### Discord Webhooks

```lua
Config.WebhookEnabled = false
Config.WebhookServerName = 'My Server'
Config.Webhook = ''                   -- Discord webhook URL
Config.DateFormat = '%d/%m/%Y [%X]'
Config.IconUrl = 'https://i.imgur.com/yourimagehere.png'
Config.BotName = 'Pilot Job Bot'
```

Webhooks log mission completions with:

* Player name and identifiers (Steam, Discord, License, FiveM)
* Mission type (passengers/rescue)
* Reward amount earned
* Timestamp

### Cooldown Settings

```lua
Config.MissionCooldown = 30           -- Seconds between missions (per player)
```

Server-side cooldown prevents spamming. Each player must wait this many seconds after completing a mission before starting another.

### Mission Definitions

Missions are defined in `Config.Missions` with two categories:

```lua
Config.Missions = {
    passengers = {
        label = L('option_passengers'),
        key = 'passengers',
        taxPrize = 3500,              -- Fee to start (0 = free)
        missions = { ... },           -- Array of mission routes
    },
    rescue = {
        label = L('option_rescue'),
        key = 'rescue',
        taxPrize = 7950,
        missions = { ... },
    },
}
```

Each individual mission:

```lua
{
    label = 'Passengers Mission',
    enableTiming = false,             -- Enable countdown timer
    timeTotal = 200,                  -- Timer seconds (if enableTiming = true)
    minRewards = 8000,                -- Minimum random reward
    maxRewards = 15000,               -- Maximum random reward
    missionPoints = {
        {
            Pos = vector3(x, y, z),   -- Checkpoint coordinates
            Type = 1,                 -- Checkpoint type
            Action = function(playerPed, vehicle, setCurrentZoneType)
                -- Custom code executed on checkpoint arrival
            end
        },
        -- ... more waypoints
    },
}
```

| Field                    | Type     | Description                            |
| ------------------------ | -------- | -------------------------------------- |
| `label`                  | string   | Mission display name                   |
| `enableTiming`           | boolean  | Enable countdown timer                 |
| `timeTotal`              | number   | Timer seconds (only if timing enabled) |
| `minRewards`             | number   | Minimum payout on completion           |
| `maxRewards`             | number   | Maximum payout on completion           |
| `missionPoints`          | table    | Array of sequential waypoints          |
| `missionPoints[].Pos`    | vector3  | World coordinates                      |
| `missionPoints[].Type`   | number   | Marker type                            |
| `missionPoints[].Action` | function | Code to run when reached               |

#### Available Action Functions

These built-in functions can be called within Action functions:

| Function                                     | Description                                     |
| -------------------------------------------- | ----------------------------------------------- |
| `joinPassengers(vehicle, spawnPos)`          | Spawn passenger NPCs and task them into vehicle |
| `joinInjuredPedPassenger(vehicle, spawnPos)` | Spawn injured ped on ground, task into vehicle  |
| `leavePassengers(vehicle)`                   | Task passenger NPCs to exit and walk away       |
| `leavePassengersInjured(vehicle)`            | Task injured ped to exit and walk away          |
| `endTimer()`                                 | Stop the countdown timer                        |
| `DrawMissionText(text, duration)`            | Show mission text notification                  |
| `FreezeEntityPosition(vehicle, state)`       | Freeze/unfreeze vehicle during boarding         |
| `PlaySound(...)`                             | Play UI sound for feedback                      |

#### Default Missions Included

**Passenger Missions (1):**

* LSIA → Sandy Shores Airfield → LSIA

**Rescue Missions (3):**

* High Mountain rescue (Chiliad area, 380s timer, $15.5k-$22.5k reward)
* East Coast rescue (Grapeseed area, 380s timer, $12.5k-$20.5k reward)
* North Mount rescue (Paleto area, 440s timer, $19.5k-$34.5k reward)

***

## How It Works

### Complete Flow

Player at mission point → Interact (E key or ox\_target) → Check job restriction → Check license → Check cooldown (server) → Show mission type menu (passengers / rescue) → Player selects type → Confirm tax payment (if taxPrize > 0) → Server validates payment → Deduct fee → Spawn/select vehicle → Teleport player into vehicle → Navigate checkpoint route: ├── Fly to checkpoint marker → Trigger Action function ├── Board/drop passengers → Proceed to next checkpoint └── Repeat until last checkpoint → Mission complete → Server validates & calculates reward → Delete/keep vehicle → Teleport back → Pay reward → Webhook log

### Mission Types

| Type           | Timer     | NPCs                       | Vehicle                    | Description              |
| -------------- | --------- | -------------------------- | -------------------------- | ------------------------ |
| **Passengers** | Optional  | Normal peds walk and board | Plane (`nimbus`)           | Standard transport route |
| **Rescue**     | ✅ Enabled | Injured ped on ground      | Helicopter (`supervolito`) | Timed medevac mission    |

### Checkpoint System

{% stepper %}
{% step %}
First checkpoint appears with a large 3D marker (configurable in `Config.CheckpointMarker`)
{% endstep %}

{% step %}
A GPS waypoint is set to the current checkpoint
{% endstep %}

{% step %}
When player enters trigger distance, the `Action` function executes
{% endstep %}

{% step %}
Next checkpoint appears automatically
{% endstep %}

{% step %}
At the final checkpoint, the mission ends
{% endstep %}
{% endstepper %}

Checkpoint loop runs at 1ms while mission is active, checking distance to current waypoint.

### Timer System (Rescue)

When `enableTiming = true`:

* Countdown starts after the first checkpoint (pickup)
* Timer text displays on screen: `~b~Time Remaining:~r~ Xs`
* Lives in a separate thread running every 1000ms
* If timer reaches 0: mission auto-cancels with fail message
* `endTimer()` stops the timer (usually called at hospital delivery)

### PilotBridge Pattern

The `PilotBridge` object abstracts framework-specific calls:

```lua
PilotBridge = {
    hasLicense = function(licenseType)     -- Check player has license
    hasJob = function(jobName)             -- Check player's current job
    openMissionMenu = function(missions)   -- Show mission selection menu
    notify = function(msg)                 -- Framework notification
}
```

| Method            | ESX                         | QBCore                                      | QBox                      |
| ----------------- | --------------------------- | ------------------------------------------- | ------------------------- |
| `hasLicense`      | `ESX.TriggerServerCallback` | `QBCore.Functions.TriggerCallback`          | `lib.callback.await`      |
| `hasJob`          | `ESX.PlayerData.job.name`   | `QBCore.Functions.GetPlayerData().job.name` | `QBX.PlayerData.job.name` |
| `openMissionMenu` | `ESX.OpenContext`           | Native NUI menu                             | `lib.registerContext`     |
| `notify`          | `ESX.ShowNotification`      | `QBCore.Functions.Notify`                   | `lib.notify`              |

### Vehicle Modes

#### Rental Mode (`RequireOwnedVehicles = false`)

{% stepper %}
{% step %}
Vehicle model from `Config.VehicleModels[missionType]` is spawned
{% endstep %}

{% step %}
Spawns at `Config.VehicleSpawnPoint` with configured heading
{% endstep %}

{% step %}
Color and fuel level are applied
{% endstep %}

{% step %}
Player is teleported into the vehicle
{% endstep %}

{% step %}
Vehicle is deleted after mission ends
{% endstep %}

{% step %}
Player teleports to `Config.TeleportBackCoords`
{% endstep %}
{% endstepper %}

#### Owned Vehicle Mode (`RequireOwnedVehicles = true`)

{% stepper %}
{% step %}
Player must already be in their own aircraft
{% endstep %}

{% step %}
Mission menu appears at `Config.OwnedVehiclesMenuPoint`
{% endstep %}

{% step %}
Vehicle is NOT deleted after mission
{% endstep %}

{% step %}
Player is NOT teleported after mission
{% endstep %}
{% endstepper %}

### Mission Cancel Detection

A background loop checks every 1000ms during active missions:

* Player not in vehicle: Mission cancels after 1 second
* Player died: Mission cancels immediately
* All spawned NPCs are cleaned up
* Vehicle is deleted (rental mode only)
* Mission state is fully reset

### Server-Side Validation

The server handles:

1. Cooldown check — `CanPlayerStartMission(playerId)` checks timestamp
2. Tax payment — Callback deducts money or rejects
3. Reward calculation — `math.random(minRewards, maxRewards)` server-side
4. Reward payment — Adds money to player's cash/bank
5. Cooldown set — `SetPlayerCooldown(playerId)` records timestamp
6. Webhook dispatch — Logs completion with player identifiers

***

## Interaction Modes

### Marker + E Key Mode

When `Config.UseOxTarget = false`:

* 3D marker renders at `Config.BlipData.pos` within `Config.DistanceToCheck`
* Help text shows within `Config.InteractionDistance` (1.5 units)
* Press E to open mission menu
* Dynamic sleep: 1ms near marker, optimized far away

### ox\_target Mode

When `Config.UseOxTarget = true`:

* Model target registered on `s_m_y_pilot_01` ped model
* Options include passenger and rescue mission types
* `canInteract` checks: mission not active, job allowed, license valid

***

## NPC Passenger System

### Passenger Transport NPCs

`joinPassengers(vehicle, spawnPos)`:

{% stepper %}
{% step %}
Selects `Config.PassengersNumber` models from shuffled `Config.PassengerModels`
{% endstep %}

{% step %}
Spawns NPCs at `spawnPos` with random heading
{% endstep %}

{% step %}
Tasks each NPC to enter the vehicle (`TaskEnterVehicle`)
{% endstep %}

{% step %}
Waits 5 seconds, then unfreezes vehicle
{% endstep %}

{% step %}
Shows "Go to next point" text
{% endstep %}
{% endstepper %}

`leavePassengers(vehicle)`:

{% stepper %}
{% step %}
Tasks each NPC to leave the vehicle (`TaskLeaveVehicle`)
{% endstep %}

{% step %}
Tasks NPCs to wander away (`TaskWanderStandard`)
{% endstep %}

{% step %}
Waits 5 seconds, then removes NPC entities
{% endstep %}

{% step %}
Clears the spawned peds list
{% endstep %}
{% endstepper %}

### Rescue Injured NPCs

`joinInjuredPedPassenger(vehicle, spawnPos)`:

{% stepper %}
{% step %}
Spawns a single `a_m_m_tramp_01` NPC at `spawnPos`
{% endstep %}

{% step %}
NPC plays `dead_a` animation from `dead` dictionary (lying on ground)
{% endstep %}

{% step %}
Engine must be off for NPC to board — shows "Turn off engine" prompt until engine is stopped
{% endstep %}

{% step %}
NPC boards when engine is off
{% endstep %}

{% step %}
Shows "Wait while the casualty boards" message, waits for entry, then displays rescue text
{% endstep %}
{% endstepper %}

`leavePassengersInjured(vehicle)`:

{% stepper %}
{% step %}
Requires engine off to disembark
{% endstep %}

{% step %}
NPC exits vehicle
{% endstep %}

{% step %}
Walks away and is cleaned up after 5 seconds
{% endstep %}
{% endstepper %}

***

## Exports & Events

### Server Exports

```lua
-- Trigger manual update check
exports['xex_pilotjob']:CheckForUpdates()
```

### Server Callbacks

| Callback                  | Parameters    | Returns   | Description                                |
| ------------------------- | ------------- | --------- | ------------------------------------------ |
| `xex_pilotjob:payTax`     | `taxAmount`   | `boolean` | Deduct mission fee from player's cash/bank |
| `xex_pilotjob:hasLicense` | `licenseType` | `boolean` | Check if player has required license       |

### Server Events

| Event                     | Parameters                    | Description                                                                                 |
| ------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------- |
| `xex_pilotjob:missionEnd` | `missionType`, `minR`, `maxR` | Complete mission — validates cooldown, calculates random reward, pays player, sends webhook |

#### `xex_pilotjob:missionEnd` Flow

```lua
-- Triggered client-side:
TriggerServerEvent('xex_pilotjob:missionEnd', missionType, minRewards, maxRewards)

-- Server-side:
-- 1. CanPlayerStartMission(src) -> checks cooldown
-- 2. math.random(minR, maxR) -> reward amount
-- 3. Add money to player (cash or bank per Config.PaymentType)
-- 4. SetPlayerCooldown(src)
-- 5. SendPilotWebhook(src, 'mission_complete', missionType, reward)
-- 6. Notify player of earnings
```

### Client Events

| Event                            | Triggered By                | Description                        |
| -------------------------------- | --------------------------- | ---------------------------------- |
| `xex_pilotjob:startMission`      | Menu selection              | Begin mission with vehicle spawn   |
| `xex_pilotjob:startMissionOwned` | Menu selection (owned mode) | Begin mission with current vehicle |

### Commands

| Command           | Context        | Description         |
| ----------------- | -------------- | ------------------- |
| `pilotjob_update` | Server Console | Manual update check |

***

## Localization

### Supported Languages

| Code | Language |
| ---- | -------- |
| `en` | English  |
| `es` | Español  |

### Locale Keys Reference

| Key                    | English                                             | Description            |
| ---------------------- | --------------------------------------------------- | ---------------------- |
| `blip_name`            | Pilots                                              | Map blip label         |
| `menu_title`           | Pilot Missions                                      | Menu header            |
| `open_menu`            | Press \[E] to show available missions               | Interaction prompt     |
| `no_air_licenses`      | You don't have the required flying license          | License check fail     |
| `job_not_allowed`      | Your current job does not allow pilot missions      | Job check fail         |
| `option_passengers`    | Passenger Transport Mission                         | Menu option label      |
| `option_rescue`        | Rescue Mission                                      | Menu option label      |
| `mission_started`      | The flight begins                                   | Mission start          |
| `mission_complete`     | You have completed the journey                      | Mission end            |
| `mission_canceled`     | Mission cancelled                                   | Cancel notification    |
| `mission_cooldown`     | You must wait before starting another mission       | Cooldown active        |
| `go_next_point`        | Go to the next point                                | Navigate to checkpoint |
| `wait_passengers`      | Wait for passengers to board                        | Boarding in progress   |
| `land_now`             | Land at the next point                              | Approach prompt        |
| `wait_next_passengers` | Wait for the next passengers to board               | Multi-stop boarding    |
| `label_taxes`          | %s (Fee: $%s)                                       | Menu label with fee    |
| `menu_confirm_title`   | Pay $%s for travel fees?                            | Fee confirmation       |
| `label_yes`            | Yes                                                 | Confirm button         |
| `label_no`             | No                                                  | Cancel button          |
| `no_money_for_taxes`   | You don't have enough money for the fees            | Insufficient funds     |
| `paid_for_taxes`       | You paid $%s in fees and reviews                    | Fee deducted           |
| `earn`                 | You earned $%s for the trip                         | Reward notification    |
| `return_vehicle`       | Return the vehicle                                  | Post-mission prompt    |
| `wait_patient`         | Wait for the injured                                | Rescue pickup          |
| `go_back_with_patient` | Take the injured person to the hospital...          | Rescue transport       |
| `patient_arrived`      | You arrived safely with the injured...              | Delivery success       |
| `no_time`              | Mission failed: time limit exceeded                 | Timer expired          |
| `seconds_remaining`    | ~~b~~Time Remaining:~~r~~ %ss                       | Timer HUD text         |
| `stop_engine`          | Turn off the engine so the injured can get off      | Engine check           |
| `stop_engine_up`       | Turn off the engine so the injured person can board | Engine check           |
| `wait_go_down`         | Wait while the wounded disembarks                   | Exit animation         |
| `wait_go_up`           | Wait while the casualty boards                      | Board animation        |
| `land_to_take`         | Land to pick up the wounded                         | Approach prompt        |
| `land_to_leave`        | Land so the wounded can leave                       | Approach prompt        |

### Adding Languages

Add a new entry in `locales.lua`:

```lua
Locales['fr'] = {
    ['blip_name'] = 'Pilotes',
    ['menu_title'] = 'Missions de Pilote',
    -- ... all keys
}
```

Then set `Config.Language = 'fr'` in `config.lua`.

***

## File Structure

```
xex_pilotjob/
├── client/
│   ├── esx.lua               # ESX client — PilotBridge, menu, license checks
│   ├── qb.lua                # QBCore client — PilotBridge, NUI menu, callbacks
│   ├── qbox.lua              # QBox client — PilotBridge with ox_lib
│   └── scrow.lua             # Shared — mission logic, NPC management, loops
├── server/
│   ├── esx.lua               # ESX server — payment callbacks, license check
│   ├── qb.lua                # QBCore server — payment callbacks, license check
│   ├── qbx.lua               # QBox server — payment with ox_lib
│   ├── scrow.lua             # Shared — cooldown, rewards, webhooks, identifiers
│   └── updater.lua           # Version checker
├── config.lua                # All configuration + mission definitions
├── locales.lua               # Translations (en/es)
├── fxmanifest.lua            # Resource manifest
├── readme.md                 # Quick start readme
└── DOCS.md                   # This documentation
```

### Key File Responsibilities

| File               | Responsibility                                                                                                                                                                         |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client/scrow.lua` | All core mission logic: `MissionState`, `startMission()`, `stopMission()`, checkpoint loop, cancel detection, NPC spawn/despawn (passengers/injured), timer thread, `PilotBridge` stub |
| `client/esx.lua`   | ESX-specific `PilotBridge` implementation (hasLicense, hasJob, openMissionMenu, notify)                                                                                                |
| `client/qb.lua`    | QBCore-specific `PilotBridge` with NUI menu system                                                                                                                                     |
| `client/qbox.lua`  | QBox-specific `PilotBridge` with ox\_lib context menus                                                                                                                                 |
| `server/scrow.lua` | `CanPlayerStartMission()`, `SetPlayerCooldown()`, `ExtractIdentifiers()`, `SendPilotWebhook()`, `playerDropped` cleanup, mission end event                                             |
| `server/esx.lua`   | ESX payment callback (`xex_pilotjob:payTax`), license callback, reward payment                                                                                                         |
| `server/qb.lua`    | QBCore payment callback, license callback, reward payment                                                                                                                              |
| `server/qbx.lua`   | QBox payment and license via ox\_lib callbacks                                                                                                                                         |

***

## Security

| Measure                 | Description                                                  |
| ----------------------- | ------------------------------------------------------------ |
| Server-side cooldown    | `CanPlayerStartMission()` prevents rapid mission starts      |
| Server-side rewards     | Random reward calculated on server, not client               |
| Server-side payment     | Tax deduction validated via callback                         |
| Cooldown per-player     | Uses player server ID, cleaned on disconnect                 |
| Double-start prevention | `MissionState.active` flag checked before starting           |
| Cancel detection        | Background loop detects vehicle exit and death               |
| Identifier extraction   | Full player identity logged (Steam, Discord, License, FiveM) |

***

## Performance

| Aspect           | Detail                                                     |
| ---------------- | ---------------------------------------------------------- |
| Idle wait        | \~2500ms between checks when far from mission point        |
| Near marker      | 1ms polling when within `DistanceToCheck`                  |
| Mission active   | 1ms checkpoint distance check loop                         |
| Cancel detection | 1000ms background check during missions                    |
| Timer thread     | 1000ms countdown loop (rescue only)                        |
| NPC cleanup      | Models released after spawn, entities deleted after wander |
| ox\_target mode  | Event-based, no polling when not interacting               |
| Cooldown cleanup | playerDropped event removes disconnected player data       |

***

## Troubleshooting

| Issue                          | Solution                                                                              |
| ------------------------------ | ------------------------------------------------------------------------------------- |
| Menu not appearing             | Check you're within `InteractionDistance` (1.5) of the blip position                  |
| License check failing          | Verify license type in `Config.LicenseNeeded` matches your framework's license system |
| Job restriction not working    | Check exact job name in `Config.JobRestriction.jobs` matches your framework           |
| Vehicle not spawning           | Verify `Config.VehicleModels` contains valid GTA model names                          |
| Passengers not boarding        | Ensure vehicle has enough seats for `Config.PassengersNumber`                         |
| Timer not stopping             | Verify `endTimer()` is called in the Action function at the hospital checkpoint       |
| Cooldown too long/short        | Adjust `Config.MissionCooldown` (in seconds)                                          |
| Webhook not sending            | Check `Config.WebhookEnabled = true` and valid Discord webhook URL                    |
| Rescue NPC won't board         | Engine must be OFF — player needs to turn off engine at pickup/dropoff                |
| Owned vehicle mode issues      | Player must already be in an aircraft before interacting                              |
| QBox errors                    | Ensure `ox_lib` is started **before** this resource                                   |
| Checkpoint not triggering      | Check `Config.CheckpointMarker.triggerDistance` is large enough                       |
| Payment going to wrong account | Check `Config.PaymentType` is set to `'cash'` or `'bank'` as desired                  |

***

## Changelog

### v2.0.0

* Complete rewrite with multi-framework support (ESX, QBCore, QBox)
* Added PilotBridge abstraction pattern
* Added ox\_target interaction mode
* Added owned vehicle mode (`Config.RequireOwnedVehicles`)
* Added server-side cooldown system
* Added configurable checkpoint markers
* Added Discord webhook logging
* Added JSON-based auto-updater
* Added rescue missions with timer system
* Added multiple mission routes per type
* Added random reward calculation (server-side)
* Added NPC passenger shuffle system
* Added injured ped mechanics (engine off requirement)
* Added job restriction and license requirement
* Improved performance with dynamic sleep optimization

### v1.0.0

* Initial release

***

## Support

| Channel | Link                                                   |
| ------- | ------------------------------------------------------ |
| Discord | [discord.gg/9yY3Jxnhh8](https://discord.gg/9yY3Jxnhh8) |

***
