# 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) |

***


---

# Agent Instructions: 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 current page URL with the `ask` query parameter:

```
GET https://jesus-gimenez.gitbook.io/xex-scripts/premium-mods/pilot-job.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
