# Installation (/advanced-garages/installation)
### Download & unzip [#download--unzip-step]
Unzip the `jg-advancedgarages-bundle` and open it - the folders inside are the resources for your server.
### Add the resources to your server [#add-the-resources-to-your-server-step]
Drag both `jg-advancedgarages` and `jg-textui` into either your `[qb]` or `[esx_addons]` folder within your server. *If you don't plan on using our new default Text UI, you don't need to transfer it to your server, and you can choose a different Text UI script in the config!*
Do not rename the `jg-advancedgarages` resource! Doing so will break the resource.
### Run the SQL install [#run-the-sql-install-step]
Run either `install/run-qb.sql` or `install/run-esx.sql` in your MySQL database, depending on your framework.
### Fix qb-phone (QBCore only) [#fix-qb-phone-qbcore-only-step]
If you are running QBCore, the default qb-phone garages app may be broken - head to the [qb-phone integration](/advanced-garages/integrations/qb-phone) to fix it.
### Configure the script [#configure-the-script-step]
Open `jg-advancedgarages/config/config.lua` and adjust the settings for your server. The file is split into sections for integrations, public garages, private garages, job garages, gang garages and impounds.
Each setting includes a comment with the available values. Keep a backup of your edited `config/config.lua` before updating the resource.
After changing the file, restart `jg-advancedgarages` or fully restart your server.
# Job & Gang Garages (/advanced-garages/job-and-gang-garages)
Gang garages are available in the QBCore version only, as QBCore has gang functionality built in. For ESX, you can set up a job garage for gang use.
Job/gang garages are shared between players who are assigned to a particular job or gang, usually via `/setjob` or `/setgang`.
There are 3 distinct garage types:
| 1. Vehicle Spawner | 2. Owned Vehicles | 3. Personal Vehicles |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A vehicle spawner garage is the most basic garage type. You can define a list of vehicles that members of the job/gang should be able to choose from, a minimum job rank for each vehicle if applicable, and a plate that you'd like each vehicle to spawn with.
All vehicles can be spawned as many times as a player wants without restriction.
| An owned vehicles job garage is where all the vehicles available in the garage to job/gang members are stored in the database, just like personal vehicles.
Job/gang members can customise the cars, but are responsible for any damage or loss while out. If a vehicle has been taken out, it cannot be taken out again if Config.JobGaragesAllowInfiniteVehicleSpawns is set to false.
| A personal vehicles garage lets you store personally owned vehicles like a standard public garage.
The difference to a public garage is that only members of the job/gang can see or interact with the garage.
|
## Setting up a garage [#setting-up-a-garage]
Open `jg-advancedgarages/config/config.lua`. Job garages are configured in `Config.JobGarageLocations`. Gang garages are configured in `Config.GangGarageLocations`.
Copy one of the existing garage entries in the correct section, then change:
* The name inside brackets. Every garage name must be unique.
* `coords` to the location where players open the garage.
* `spawn` to one or more vehicle spawn locations.
* `job` or `gang` to the in-game job or gang name.
* `type` to `"car"`, `"sea"` or `"air"`.
* `distance` to the radius where players can use the garage.
* `vehiclesType` to `"personal"`, `"owned"` or `"spawner"`.
You can allow more than one job or gang by adding each name to the table, such as `job = {"police", "ambulance"}`.
Only private garages have an in-game creation command. Use `/privategarages` to create a private garage. Public, job, gang and impound locations must be added in `config/config.lua`.
## Personal Vehicle Garages [#personal-vehicle-garages]
These work just like public garages. Set the job/gang garage to store personal vehicles, and players can store their personal vehicles as usual. Players who aren't part of the job/gang can't see or interact with the garage.
## Owned Vehicle Garages [#owned-vehicle-garages]
If a job buys a vehicle through JG Dealerships using society funds, store it in an Owned Vehicles garage. The garage must have `vehiclesType = "owned"`. This makes the purchased vehicle available to members of that job. It won't store in a Vehicle Spawner garage with `vehiclesType = "spawner"`.
Once you've added an owned vehicle garage to `Config.JobGarageLocations` or `Config.GangGarageLocations`, add vehicles to it in-game:
1. **As an admin in-game**, get a car into your personal garage by either purchasing it from a vehicle store, or by spawning it in with `/car` and then adding it to your garage with `/admincar`. Or, get in an owned car from another player.
2. Use the command `/setjobvehicle [job_name] [min_job_grade]` or \
`/setgangvehicle [gang_name] [min_gang_grade]` to add this owned vehicle to the shared job/gang garage respectively.
3. Admins can use `/removejobvehicle [new_player_owner_id]` while in the car to give it back to a player and make it a personal vehicle again.
## Vehicle Spawner Garages [#vehicle-spawner-garages]
Set `vehiclesType = "spawner"`, then add the available vehicles to the garage's `vehicles` table in `config/config.lua`:
```lua title="config/config.lua"
vehiclesType = "spawner",
showLiveriesExtrasMenu = true,
vehicles = {
[1] = {
model = "police",
plate = "PD",
minJobGrade = 0,
nickname = "Police car",
livery = 1,
modLivery = 1,
extras = {1, 2},
maxMods = true
}
}
```
Set `plate = false` to generate a random plate. Don't use a fixed plate that already exists in your database. Restart `jg-advancedgarages` after changing the file.
# Languages (/advanced-garages/languages)
{/* Generated by scripts/update-languages.mjs from the jgscripts/translations
repository - do not edit by hand, changes will be overwritten. */}
JG Advanced Garages is available in 19 languages, translated by the community.
## Setting the language [#setting-the-language]
In `config.lua`, set `Config.Locale` to the code of the language you want, then restart the resource:
```lua
Config.Locale = "en"
```
## Available languages [#available-languages]
| Language | `Config.Locale` value |
| --------------------- | --------------------- |
| English | `en` |
| Arabic | `ar` |
| Chinese (Simplified) | `cn` |
| Chinese (Traditional) | `zh-tw` |
| Czech | `cs` |
| Danish | `da` |
| Dutch | `nl` |
| Finnish | `fi` |
| French | `fr` |
| German | `de` |
| Hungarian | `hu` |
| Italian | `it` |
| Japanese | `ja` |
| Lithuanian | `lt` |
| Portuguese | `pt` |
| Romanian | `ro` |
| Spanish | `es` |
| Swedish | `sv` |
| Vietnamese | `vi` |
## Contributing a translation [#contributing-a-translation]
Spotted a mistake, or want the script in your language? Translations live in our public [translations repository](https://github.com/jgscripts/translations), and anyone can contribute. See the [Translations](/getting-started/translations) guide for how to submit yours.
# Migration from v1 (/advanced-garages/migration-from-v1)
Ignore this guide unless you are migrating from **v1** of the resource.
Thank you so much for continuing to use Advanced Garages. Your ongoing support is why we keep developing this script.
To migrate, you only need to make a few SQL changes, listed below. Advanced Garages still pulls from the default vehicles table, so you won't lose any of your data if you're migrating from Advanced Garages v1.
Run the following SQL for the new job/gang ownership check:
```sql
UPDATE `player_vehicles` SET `citizenid` = `license` WHERE `license` NOT LIKE 'license:%';
UPDATE `player_vehicles` SET `damage` = '';
```
Run the following SQL to add the columns that used to be QB only. Since the script is now universal, you need these for it to work.
```sql
ALTER TABLE `owned_vehicles` ADD COLUMN IF NOT EXISTS `gang_vehicle` TINYINT(1) DEFAULT '0';
ALTER TABLE `owned_vehicles` ADD COLUMN IF NOT EXISTS `gang_vehicle_rank` INT(10) DEFAULT '0';
UPDATE `owned_vehicles` SET `damage` = '';
```
*Gang garage functionality still isn't included, as gangs aren't part of ESX by default.*
Don't reuse your v1 `config.lua`. Start with the current `config/config.lua` included with the resource, then copy your settings and garage locations into the matching sections one at a time. This prevents removed or renamed v1 settings from breaking the current version.
# Public, Private & Impound Garages (/advanced-garages/public-private-and-impound-garages)
## Public Garages [#public-garages]
* You can configure the prices of transfers to other garages, and of returning vehicles to the garage if they are left out (or make them free).
* There are 3 different types of garages - `car`, `sea` and `air`. For players who own planes, helicopters or boats, you'll need to add air and sea garages so that players can retrieve these vehicles, as they can't retrieve them from car garages *(hint: there are example air and sea garages already added in the default config)*.
* Vehicle types are decoded from the meta files included with a vehicle - if your addon plane or boat is showing in the wrong garage, check the `vehicles.meta` for that addon vehicle.
* You can configure the distance at which the instruction overlays appear when you're near the garage.
## Private Garages [#private-garages]
* Private garages are the only garage type created in-game. Admins or players with an enabled job can use `/privategarages`, controlled by `Config.PrivGarageCreateCommand` and `Config.PrivGarageCreateJobRestriction` in `config/config.lua`.
* Only the player selected in the step above can access the garage.
* To delete a garage, remove it from the `player_priv_garages` table in the database. A better way of handling this is coming soon.
## Impound [#impound]
* Players with an enabled job (see `Config.ImpoundJobRestriction`) can use `/iv` when next to or inside a car to send it to the impound.
* You can set a reason and whether the owner can retrieve it themselves. If this is set to false, only players with a whitelisted job can remove it from the impound for them.
* If self-retrieval is enabled, you can set a time before they can take it out and a price they must pay (or 0).
* The owner of the vehicle can see in their garage that it has been impounded, the reason why and by who, to avoid any confusion.
* You can add one or more impound locations in `config/config.lua`. Vehicles that are impounded are available at every impound location.
* You can also add impounds for `air` and `sea` vehicles by setting their type in `config/config.lua`. If type is not provided, it will default to `car`, and only `car` vehicle types will show in the impound.
# Transferring Vehicles (/advanced-garages/transferring-vehicles)
## Between Players [#between-players]
By default, players can transfer their vehicle to any player that's online, from a dropdown list. The player does not have to accept the transfer.
To hide names to prevent metagaming: `Config.TransferHidePlayerNames = false`
To disable the feature, see [Disabling Transfers](/advanced-garages/transferring-vehicles#disabling-transfers).
## Between Garages [#between-garages]
Vehicles are available from the garage they were last stored in. By default though, players can transfer vehicles between garages for a configurable fee.
Adjust the fee: `Config.GarageVehicleTransferCost`
To disable the feature, see [Disabling Transfers](/advanced-garages/transferring-vehicles#disabling-transfers).
## Blacklist Transfers Between Players [#blacklist-transfers-between-players]
You may want to allow transfers between players, but prevent this for certain vehicles (such as donator or other sensitive vehicles). Add the spawn code of the vehicle to `Config.PlayerTransferBlacklist`:
```lua
Config.PlayerTransferBlacklist = {
"adder",
"thrax"
}
```
## Disabling Transfers [#disabling-transfers]
```lua
Config.EnableTransfers = {
betweenGarages = false, -- disables transfers between garages
betweenPlayers = false -- disables transfers between players
}
```
# Vehicle Duplication Prevention (/advanced-garages/vehicle-duplication-prevention)
Vehicle duplication prevention improves the roleplay experience by only allowing one vehicle to be taken out at a time, just like in real life. This feature works across public, private, job and gang garages.
To enable the feature, set the infinite vehicle spawn options in `config/config.lua` to `false`. You can control public and private, job, and gang garages separately:
```lua title="config/config.lua"
Config.AllowInfiniteVehicleSpawns = false
Config.JobGaragesAllowInfiniteVehicleSpawns = false
Config.GangGaragesAllowInfiniteVehicleSpawns = false
```
Restart `jg-advancedgarages` after changing these settings.
## Behaviour [#behaviour]
* When taking out a vehicle, the script stores the vehicle ID on the server so that all clients are aware the vehicle is out. A client restart will not put a vehicle back in the garage.
* If a vehicle is completely destroyed or de-spawned, it will be returned to the garage menu (for a fee, if configured).
* This works for job/gang garages too - as all clients are aware a vehicle is out, another job member can't spawn it while it's out.
## Known Issues [#known-issues]
A perfect vehicle duplication system is extremely difficult to get right, as many scripts have found. Since moving to server-side vehicle spawning, there are only a couple of known issues, due to FiveM limitations. Vehicles that are blocked in, are underwater or are upside down will NOT be respawnable, as they still technically have health.
**If this happens, players can contact an admin, who can use `/vreturn [plate]` to return the vehicle to the garage.** If this is an issue, it may be best to allow infinite respawns in the config.
# Vehicle Images (/advanced-garages/vehicle-images)
Only available in v2.2.0 or newer
You can show little vehicle thumbnails in the garage UI, as you can see below. These automatically stay up to date with the latest DLC vehicles.
Vehicle thumbnails
To enable this feature, set `Config.ShowVehicleImages = true`
If you have addon vehicles and want to add images for them too:
1. There is a folder in the root of the script called `vehicle_images`
2. Get a **.png** (has to be a png) of the addon vehicle (recommended 140x100px)
3. Drag the .png into the `vehicle_images` folder
4. Rename the file to the spawn code/model name of the vehicle - for example `adder.png`
5. Go to `fxmanifest.lua` and add `"vehicle_images/*",` into the files table like this:
If you are using vehicle images within JG Dealerships, you don't need to duplicate them. JG Advanced Garages will automatically pull those images, so long as the feature is enabled within both scripts.
# Adding Vehicles (/dealerships/adding-vehicles)
Create your [dealership locations](/dealerships/setting-up-locations) before adding vehicles. Imports use each location's ID or categories to decide where vehicles are available.
## Importing vehicles [#importing-vehicles]
### Open the vehicle list [#open-the-vehicle-list-step]
Open the admin panel with `/dealeradmin`, then select **Vehicles** in the left sidebar.
### Choose an import source [#choose-an-import-source-step]
Click **Import** and select the source for your framework. The panel checks that the source is available and shows how many vehicles it found.
See the framework details below if the source is unavailable or the count is wrong.
### Choose the import behaviour [#choose-the-import-behaviour-step]
Select one of these options:
* **Append** adds vehicles whose spawn codes aren't already in JG Dealerships. Existing vehicles and dealership data are left unchanged.
* **Overwrite** deletes the existing JG Dealerships vehicle list, display vehicles, stock, orders and sales before importing the selected source.
Use **Append** unless you intend to replace all existing dealership vehicle data. Back up your database before using **Overwrite**.
### Choose how vehicles are assigned [#choose-how-vehicles-are-assigned-step]
If the source includes `shop` data, choose one of these stock assignment methods:
* **Use dealership IDs from source** assigns each vehicle using its `shop` value. Every value must match the ID of a location in JG Dealerships.
* **Match by vehicle category** assigns each vehicle to locations that include its category.
The `shop` option isn't shown when the source has no `shop` data. ESX imports always match vehicles by category.
### Run the import [#run-the-import-step]
Click **Import**. When it finishes, check the vehicle list and confirm the vehicles are assigned to the correct dealerships.
### Framework import details [#framework-import-details]
Select **QBCore Shared Vehicles**. This is available when `Config.Framework` is set to `QBCore` and `QBCore.Shared.Vehicles` has loaded.
The import reads these values from each vehicle:
* table key: spawn code
* `brand`: brand name
* `name`: model name shown to players
* `category`: vehicle category
* `price`: base price
* `shop`: optional dealership ID or list of dealership IDs
These vehicles are normally configured in `qb-core/shared/vehicles.lua`. Fix any invalid entries in that file and restart `qb-core` before importing again.
Select **Qbox Shared Vehicles**. This is available when `Config.Framework` is set to `Qbox` and the `qbx_core` vehicle export has loaded.
The import reads these values from each vehicle:
* `model`: spawn code
* `brand`: brand name
* `name`: model name shown to players
* `category`: vehicle category
* `price`: base price
* `shop`: optional dealership ID or list of dealership IDs
If the source is unavailable, make sure `qbx_core` starts before `jg-dealerships`, then fully restart your server.
Select **ESX Vehicles Table**. This is available when `Config.Framework` is set to `ESX` and your database contains the `vehicles` table.
The import reads the `model`, `name`, `category` and `price` columns. ESX doesn't provide a brand or dealership ID through this table, so vehicles are assigned to locations by category.
If no vehicles are found, check that you're using the correct database and that the `vehicles` table contains rows.
## Adding vehicles manually [#adding-vehicles-manually]
Use this method for individual vehicles that aren't in your framework's vehicle list, such as addon vehicles.
### Open the add vehicle form [#open-the-add-vehicle-form-step]
Open `/dealeradmin`, select **Vehicles**, then click **Add** in the top-right corner.
### Enter the vehicle details [#enter-the-vehicle-details-step]

Complete these fields:
* **Spawn Code** is the vehicle's exact model spawn name, such as `adder`. Start the addon vehicle resource before adding it so the panel can validate the spawn code.
* **Brand** is the manufacturer shown to players. This is optional.
* **Model** is the vehicle name shown to players.
* **Category** controls how the vehicle is grouped and filtered. The available options come from `Config.Categories` in `config/config.lua`.
* **Price** is the base price. It is also used as the starting price for each selected dealership.
* **Overwrite dealership prices** is used when editing a saved vehicle. It changes existing dealership prices to the new base price.
* **Enable Price Limits** lets you set the minimum and maximum prices that dealership managers can choose.
* **Unlimited Stock** removes the global ordering limit. Turn it off to set the maximum number that can be ordered across all dealerships.
* **Dealerships** controls which locations stock the vehicle. Select every location where it should be available.
### Save the vehicle [#save-the-vehicle-step]
Click **Add** at the bottom of the form. The vehicle should appear in the list with its category, price and assigned dealerships.
# Job & Gang Whitelists (/dealerships/advanced-configuration)
Society Purchase is only available if you own JG Advanced Garages.
Set up a matching bank account before enabling Society Purchase. The account name must match the internal job or gang name in the whitelist. See [Society banking](/dealerships/integrations/society-banking).
Gang configuration is not available for ESX
## Information about whitelists [#information-about-whitelists]
1. Showroom Job Whitelist = Restrict specific jobs to open a dealership
2. Showroom Gang Whitelist = Restrict specific gangs to open a dealership
3. Society Purchase Job Whitelist = Use job society funds to purchase
4. Society Purchase Gang Whitelist = Use gang society funds to purchase
## Configuring a location's whitelist [#configuring-a-locations-whitelist]
To set up job or gang restricted dealerships and society purchases, head to the "Advanced" tab of the in-game location editor.
1. Enter the chat command `/dealeradmin`
2. Click the pencil icon (edit) of the location you want to add the whitelist to
3. Click the "Advanced" tab
4. Towards the bottom, you'll see the 4 whitelist options. Enter the job name, and if it's valid, you can also select the job/gang ranks that will have access.
# Custom Payment Options (/dealerships/custom-payment-options)
Available in Dealerships v2.0 or newer.
Add custom payment methods to your dealership, such as VIP Coins, Tokens, or any other currency from your server.
## Quick Start [#quick-start]
1. Open `framework/sv-currencies.lua`
2. Scroll to the **bottom** of the file
3. Find the commented example (`-- VIP Coins`)
4. Copy and modify it for your currency
## Step-by-Step Guide [#step-by-step-guide]
### Open the File [#open-the-file-step]
Open `framework/sv-currencies.lua` in any text editor.
Scroll all the way to the bottom. You'll see a commented-out example that looks like this:
```lua
-- ============================================================================
-- EXAMPLE: VIP Coins Custom Currency
-- Uncomment and modify this to add your own custom currency!
-- ============================================================================
-- Currencies.Server.Register({
-- id = "vip_coins",
-- ...
-- })
```
### Copy the Example [#copy-the-example-step]
Copy the entire example block and paste it below. Remove the `--` comment markers to activate it.
### Fill in the Fields [#fill-in-the-fields-step]
Here's what each field means:
| Field | Required? | What it does |
| ---------------- | --------- | ---------------------------------------------------- |
| `id` | Yes | Unique identifier (e.g., `"vip_coins"`, `"tokens"`) |
| `label` | Yes | Display name shown to players (e.g., `"VIP Coins"`) |
| `format` | Yes | How to display amounts (see examples below) |
| `conversionRate` | Yes\* | How much 1 unit is worth in dollars |
| `flatCost` | No | Fixed cost per purchase (overrides `conversionRate`) |
| `allowFinance` | Yes | Can players finance with this currency? |
| `fetchBalance` | Yes | Function to get player's balance |
| `addBalance` | Yes | Function to add to player's balance |
| `removeBalance` | Yes | Function to remove from player's balance |
\*Required unless using `flatCost`
## Understanding the Fields [#understanding-the-fields]
### `format` - How Prices Are Displayed [#format---how-prices-are-displayed]
The `%s` is replaced with the number:
| Format | Example Output |
| ------------- | -------------- |
| `"$%s"` | $1,500 |
| `"%s coins"` | 1,500 coins |
| `"%s Tokens"` | 1,500 Tokens |
| `"💎 %s"` | 💎 1,500 |
### `conversionRate` - Currency Value [#conversionrate---currency-value]
This determines how much 1 unit of your currency is worth in base dollars:
| Rate | Meaning | $50,000 car costs... |
| ------- | ---------------- | -------------------- |
| `1` | 1 coin = $1 | 50,000 coins |
| `100` | 1 coin = $100 | 500 coins |
| `10000` | 1 coin = $10,000 | 5 coins |
| `50000` | 1 coin = $50,000 | 1 coin |
### `flatCost` - Fixed Price Per Purchase (Optional) [#flatcost---fixed-price-per-purchase-optional]
If you want **every vehicle to cost the same amount** regardless of its price, use `flatCost`:
```lua
flatCost = 1, -- Every vehicle costs 1 token
```
With `flatCost = 1`:
* A $10,000 car costs **1 token**
* A $500,000 car costs **1 token**
When using `flatCost`, set `allowFinance = false` (you can't split 1 token into payments!)
### `allowFinance` - Financing Support [#allowfinance---financing-support]
* `true` = Players can use this currency for financed purchases
* `false` = Full payment only
Must be `false` if using `flatCost`
## Balance Functions [#balance-functions]
You need to tell the system how to check and modify player balances. This depends on how your currency is stored.
### Example: Using a Database Table [#example-using-a-database-table]
If you store coins in a `player_coins` table:
```lua
fetchBalance = function(src)
local identifier = Framework.Server.GetPlayerIdentifier(src)
local result = MySQL.scalar.await(
"SELECT coins FROM player_coins WHERE identifier = ?",
{ identifier }
)
return tonumber(result) or 0
end,
addBalance = function(src, amount)
local identifier = Framework.Server.GetPlayerIdentifier(src)
MySQL.update.await(
"UPDATE player_coins SET coins = coins + ? WHERE identifier = ?",
{ amount, identifier }
)
return true
end,
removeBalance = function(src, amount)
local identifier = Framework.Server.GetPlayerIdentifier(src)
MySQL.update.await(
"UPDATE player_coins SET coins = coins - ? WHERE identifier = ?",
{ amount, identifier }
)
return true
end,
```
### Example: Using an Export from Another Resource [#example-using-an-export-from-another-resource]
If another script manages your currency:
```lua
fetchBalance = function(src)
return exports["my-coin-system"]:GetPlayerCoins(src) or 0
end,
addBalance = function(src, amount)
return exports["my-coin-system"]:AddCoins(src, amount)
end,
removeBalance = function(src, amount)
return exports["my-coin-system"]:RemoveCoins(src, amount)
end,
```
## Complete Examples [#complete-examples]
### Example 1: VIP Coins (Conversion Rate) [#example-1-vip-coins-conversion-rate]
1 VIP Coin = $10,000
```lua
Currencies.Server.Register({
id = "vip_coins",
label = "VIP Coins",
format = "%s coins",
conversionRate = 10000,
allowFinance = false,
fetchBalance = function(src)
return exports["vip-system"]:GetCoins(src) or 0
end,
addBalance = function(src, amount)
exports["vip-system"]:AddCoins(src, amount)
return true
end,
removeBalance = function(src, amount)
exports["vip-system"]:RemoveCoins(src, amount)
return true
end,
})
```
### Example 2: Car Tokens (Flat Cost) [#example-2-car-tokens-flat-cost]
1 Token = 1 Vehicle (any price)
```lua
Currencies.Server.Register({
id = "car_token",
label = "Car Token",
format = "%s Token(s)",
conversionRate = 1,
flatCost = 1,
allowFinance = false,
fetchBalance = function(src)
local identifier = Framework.Server.GetPlayerIdentifier(src)
local result = MySQL.scalar.await(
"SELECT tokens FROM player_tokens WHERE identifier = ?",
{ identifier }
)
return tonumber(result) or 0
end,
addBalance = function(src, amount)
local identifier = Framework.Server.GetPlayerIdentifier(src)
MySQL.update.await(
"UPDATE player_tokens SET tokens = tokens + ? WHERE identifier = ?",
{ amount, identifier }
)
return true
end,
removeBalance = function(src, amount)
local identifier = Framework.Server.GetPlayerIdentifier(src)
MySQL.update.await(
"UPDATE player_tokens SET tokens = tokens - ? WHERE identifier = ?",
{ amount, identifier }
)
return true
end,
})
```
### Example 3: Premium Points (with Financing) [#example-3-premium-points-with-financing]
1 Point = $1,000, supports financing
```lua
Currencies.Server.Register({
id = "premium_points",
label = "Premium Points",
format = "%s pts",
conversionRate = 1000,
allowFinance = true,
fetchBalance = function(src)
return exports["premium-shop"]:GetPoints(src) or 0
end,
addBalance = function(src, amount)
exports["premium-shop"]:AddPoints(src, amount)
return true
end,
removeBalance = function(src, amount)
exports["premium-shop"]:RemovePoints(src, amount)
return true
end,
-- Required for financing: offline balance functions
fetchBalanceOffline = function(identifier)
local result = MySQL.scalar.await(
"SELECT points FROM premium_points WHERE identifier = ?",
{ identifier }
)
return tonumber(result) or 0
end,
removeBalanceOffline = function(identifier, amount)
MySQL.update.await(
"UPDATE premium_points SET points = points - ? WHERE identifier = ?",
{ amount, identifier }
)
return true
end,
})
```
## Enabling Your Currency at a Dealership [#enabling-your-currency-at-a-dealership]
After adding your currency, enable it for each dealership:
1. Open the **Admin Panel** (`/dealeradmin`)
2. Go to **Locations**
3. Click on a dealership
4. Go to the **Advanced** tab
5. Enable your new payment method in the **Payment Methods** section
6. Save
## Troubleshooting [#troubleshooting]
* Make sure you've enabled the currency in the dealership's **Advanced** settings
* Restart the resource after adding a new currency
* Check your `conversionRate` value
* If using `flatCost`, make sure it's set correctly
* Check your `fetchBalance` function returns the correct number
* Check your `removeBalance` function returns `true` on success
* Check the server console for errors
* Make sure `allowFinance = true`
* Make sure `flatCost` is NOT set (flat cost currencies can't be financed)
* Add `fetchBalanceOffline` and `removeBalanceOffline` functions for automatic payments
# Employees (/dealerships/employees)
You can hire employees in an **owned dealership** via the Dealership Management > Employees tab.
Employees can be one of three ranks: Manager, Supervisor or Sales. You cannot change this, but you can configure how these align to the job ranks in your framework via `framework/sv-functions.lua`.
Here is the permissions breakdown:
# Installation (/dealerships/installation)
### Download & unzip [#download--unzip-step]
Unzip the `jg-dealerships-bundle` and open it - the folders inside are the resources for your server.
### Add the resources to your server [#add-the-resources-to-your-server-step]
Drag `jg-dealerships` into a new folder called `[jg]` within your server's `resources` folder.
If you don't plan on using our new default Text UI, `jg-textui`, you don't need to transfer it to your server. You can choose a different Text UI script in the config.
### Update your server.cfg [#update-your-servercfg-step]
In your `server.cfg`, add a new line **after** all your other resources have started:
```txt
ensure [jg]
```
You should probably disable `qb-vehicleshop` or `esx_vehicleshop` because there will be location clashes.
### Migrating from v1? [#migrating-from-v1-step]
If you're upgrading and want to keep all of your existing data, stop here and follow [Migrating from v1](/dealerships/migrating-from-v1) first.
### Configure the script [#configure-the-script-step]
Core options live in `config/config.lua`. Most are set to auto, so in some cases you may not need to edit this file at all - the important ones are **Localisation**, **Framework & Integrations** and **Interaction Methods**. Custom dealership categories are also added here, under `Config.Categories`.
Locations and vehicles are managed entirely in-game - see [Setting Up Locations](/dealerships/setting-up-locations) and [Adding Vehicles](/dealerships/adding-vehicles) next.
### Set up business banking [#set-up-business-banking-step]
`Config.UseFrameworkJobs = true` is enabled by default. With it enabled, every dealership business needs a society bank account that matches its internal job name.
Follow [Society Banking](/dealerships/integrations/society-banking) before staff use the business. The same page covers accounts used by Society Purchase.
## Troubleshooting [#troubleshooting]
JG Dealerships tries to automatically make the required database changes. In some cases, this automatic installation fails and you need to make the changes manually.
Head into the `install` folder within `jg-dealerships`. Run either the `run-qb.sql` (QBCore & Qbox) or `run-esx.sql` (ESX) file in your database software (such as PhpMyAdmin or HeidiSQL).
If you get an error along the lines of `cannot use the syntax IF NOT EXISTS`, remove every instance of `IF NOT EXISTS` from the run.sql file and re-run it. It will still run fine.
Make sure you are running this SQL code within the correct database - triple check and cross reference the name of the database!
Old or partially migrated v2 tables can have a different structure from the version of JG Dealerships you're running. This can cause SQL or callback errors when you open a dealership, or leave the dealer-admin vehicle list stuck loading.
This reset deletes your dealership locations, vehicles, employees, sales and other dealership data. Back up your full database before continuing.
1. Stop `jg-dealerships`.
2. Create a full database backup and check that you can access the backup file.
3. Export `dealership_locations` as data only in a second SQL file. Enable complete or named inserts so each `INSERT` lists its columns. Don't include the old table structure.
4. In your database software, delete only the tables whose names start with `dealership_`. Don't delete framework tables such as `player_vehicles` or `owned_vehicles`.
5. Open the `install` folder in your current copy of `jg-dealerships`. Run `run-qb.sql` for QBCore or Qbox, or `run-esx.sql` for ESX.
6. Start `jg-dealerships`. Check that you can open a dealership and load the vehicle list.
7. If both work, stop `jg-dealerships` and import the saved location rows into the new `dealership_locations` table. Don't replace the new table structure with the old one.
8. Start `jg-dealerships` and check your restored locations.
If the location import reports a missing or invalid column, stop and contact support. Don't force the import or restore the old table structure. Your full backup is the recovery copy if you need to roll back.
# Languages (/dealerships/languages)
{/* Generated by scripts/update-languages.mjs from the jgscripts/translations
repository - do not edit by hand, changes will be overwritten. */}
JG Dealerships is available in 17 languages, translated by the community.
## Setting the language [#setting-the-language]
In `config.lua`, set `Config.Locale` to the code of the language you want, then restart the resource:
```lua
Config.Locale = "en"
```
## Available languages [#available-languages]
| Language | `Config.Locale` value |
| -------------------- | --------------------- |
| English | `en` |
| Arabic | `ar` |
| Chinese (Simplified) | `cn` |
| Czech | `cs` |
| Dutch | `nl` |
| French | `fr` |
| German | `de` |
| Hungarian | `hu` |
| Italian | `it` |
| Japanese | `ja` |
| Lithuanian | `lt` |
| Norwegian | `no` |
| Polish | `pl` |
| Portuguese | `pt` |
| Spanish | `es` |
| Swedish | `sv` |
| Turkish | `tr` |
## Contributing a translation [#contributing-a-translation]
Spotted a mistake, or want the script in your language? Translations live in our public [translations repository](https://github.com/jgscripts/translations), and anyone can contribute. See the [Translations](/getting-started/translations) guide for how to submit yours.
# License Check (/dealerships/license-check)
This guide currently only works with JG Dealerships V2.
First, add the following to `config.lua`.
```lua
-- License Requirements
-- Configure license requirements for dealerships
-- Note: In V2, dealership locations are stored in the database
-- Use the dealership ID (UUID) from the dealership_locations to configure license requirements
Config.DealershipLicenses = {
["b4dbbcca-a7ba-462a-b772-3c3e6434612c"] = { -- Replace with your dealership ID
enabled = true,
licenseType = "driver" -- The license type required (e.g., "boat", "pilot", "driver")
},
}
```
Replace the `ShowroomPreCheck(dealershipId)` function in `config-cl.lua` with the code below.
```lua
---@param dealershipId string
---@return boolean allowed
function ShowroomPreCheck(dealershipId)
local licenseConfig = lib.callback.await("jg-dealerships:server:get-dealership-license-config", false, dealershipId)
if not licenseConfig then
return true
end
if not licenseConfig.enabled then
return true
end
local Player = Framework.Client.GetPlayerData()
local hasLicense = Player.metadata and Player.metadata['licences'] and Player.metadata['licences'][licenseConfig.licenseType]
if not hasLicense then
local msg = "You require a " .. licenseConfig.licenseType .. " license to access this showroom."
Framework.Client.Notify(msg, "error", 5000)
return false
end
return true
end
```
```lua
---@param dealershipId string
---@return boolean allowed
function ShowroomPreCheck(dealershipId)
local licenseConfig = lib.callback.await("jg-dealerships:server:get-dealership-license-config", false, dealershipId)
if not licenseConfig then
return true
end
if not licenseConfig.enabled then
return true
end
local hasLicense = lib.callback.await('jg-dealerships:server:check-player-license', false, licenseConfig.licenseType)
if not hasLicense then
local msg = "You require a " .. licenseConfig.licenseType .. " license to access this showroom."
Framework.Client.Notify(msg, "error", 5000)
return false
end
return true
end
```
Add the code below to `config-sv.lua` for this integration to work.
```lua
---@param src integer
---@param dealershipId string
---@return table|nil configuration { enabled: boolean, licenseType: string }
lib.callback.register("jg-dealerships:server:get-dealership-license-config", function(src, dealershipId)
if not dealershipId then
return nil
end
local licenseConfig = Config.DealershipLicenses[dealershipId]
if not licenseConfig then
return nil
end
return licenseConfig
end)
```
```lua
---Get dealership license configuration
---@param src integer
---@param dealershipId string
---@return table|nil configuration { enabled: boolean, licenseType: string }
lib.callback.register("jg-dealerships:server:get-dealership-license-config", function(src, dealershipId)
if not dealershipId then
return nil
end
local licenseConfig = Config.DealershipLicenses[dealershipId]
if not licenseConfig then
return nil
end
return licenseConfig
end)
---Check if player has a specific license (supports both ESX and QB/Qbox)
---@param src integer
---@param licenseType string
---@return boolean hasLicense
lib.callback.register('jg-dealerships:server:check-player-license', function(src, licenseType)
if not licenseType then
return false
end
-- ESX: Check user_licenses table in database
if Config.Framework == "ESX" then
local identifier = Framework.Server.GetPlayerIdentifier(src)
local result = MySQL.scalar.await(
'SELECT type FROM user_licenses WHERE type = ? AND owner = ?',
{licenseType, identifier}
)
return result ~= nil
end
-- QB/Qbox: Check player metadata
if Config.Framework == "QBCore" or Config.Framework == "Qbox" then
local Player = Framework.Server.GetPlayer(src)
if not Player then
return false
end
return Player.PlayerData.metadata and
Player.PlayerData.metadata['licences'] and
Player.PlayerData.metadata['licences'][licenseType] or false
end
return false
end)
```
# Migrating from v1 (/dealerships/migrating-from-v1)
This guide is only useful if you want to keep all the data from your v1 installation. If you want to start from scratch with v2, follow the installation guide instead.
**Back up everything before you start!** Dealerships v2 is new, and there is a risk you could lose your existing data. Back up your existing database table, and your previous working version of JG Dealerships v1.
## Before You Begin [#before-you-begin]
Back up your existing `config.lua` file before proceeding. You'll need it later.
### Replace Script Files [#replace-script-files-step]
1. Delete all existing files in your `jg-dealerships` folder
2. Extract the new v2 files downloaded from Portal into the folder
3. Copy your backed-up `config.lua` into `config/config.lua`, replacing the new one
### Add New Configuration Options [#add-new-configuration-options-step]
Add the following new v2 options to the bottom of your `config.lua`:
```lua
Config.UseFrameworkJobs = false
Config.InteractionMethod = "textui" -- or "target", "3dtextui", "radial"
Config.Target = "auto" -- or "ox_target"
Config.DrawText3d = "auto" -- or "sleepless_interact"
Config.RadialMenu = "auto" -- or "ox_lib"
Config.BlipNameFormat = "Dealership: %s"
Config.EntityStreamingDistance = 100.0 -- Distance in meters at which entities despawn/respawn
Config.TruckingMissionForOrderDeliveries = true
Config.DealershipMaxActiveTestDrives = 5 -- Maximum number of active test drives per dealership
Config.EmployeePermissions = {
["Manager"] = {
"ADMIN", -- Full access
},
["Supervisor"] = {
"MANAGE_INVENTORY",
"VIEW_RECORDS",
"SELL",
"DELIVER",
},
["Sales"] = {
"SELL",
"VIEW_RECORDS",
"DELIVER",
},
}
```
### Restart and Migrate Database [#restart-and-migrate-database-step]
1. Fully restart your server
2. Run the migration command using **one** of these methods:
* Type `/migratev2` in the in-game chat
* Run `migratev2` in the txAdmin server console
### Import Your Locations [#import-your-locations-step]
1. Navigate to `/dealeradmin` in-game
2. Click the **Import** button
3. Select **"Existing Config"** (described as: *Import locations from your config.lua (for v1 migration)*)
4. Follow the import wizard to complete the process
## Done! [#done]
Your v1 data should now be migrated to v2. Verify your dealership locations and check all functionality is working as expected.
# Plate Format (/dealerships/plate-format)
Requires Dealerships v1.1.5 or newer
When players purchase a new vehicle, you can customise the vehicle licence plate that will be generated for them. Your car plate can have letters, numbers, and spaces. **It should be up to 8 characters long.**
## How to Write Your Plate Format [#how-to-write-your-plate-format]
* Use `A` for any random letter.
* Use `1` for any random number.
* Want a specific letter or number to always show up? Put a `^` before it.
## Examples [#examples]
* **All Random Letters**: **`AAAAAAAA`** gives something like `GHTPAXZQ`.
* **Mix of Letters, Spaces and Numbers**: **`AA11 1AA`** makes something like `GH49 8KJ`.
* **Fixed Letter or Number**: **`^Z123 A^B1`** always has `Z` at the start and `B` in the second part, like `Z456 XB4`.
* For example, in the UK you would have a fixed year section, so you could use `AA^2^4 AAA`, which would give something like `MA24 UAW`.
## Quick Steps [#quick-steps]
1. Open the `config.lua` file
2. Find `Config.PlateFormat`
3. Change it using `A` for letters, `1` for numbers, and `^` for fixed characters.
## Customisation [#customisation]
If you're a developer and would like more control, the function is open source (`Framework.Server.VehicleGeneratePlate`) and can be found in `framework/sv-functions.lua`.
# Setting Up Locations (/dealerships/setting-up-locations)
Dealership locations are created and managed entirely in-game. No configuration required!
### Open the admin panel [#open-the-admin-panel-step]
Open the admin panel with `/dealeradmin`
### Open locations [#open-locations-step]
Navigate to **Locations**
### Create a location [#create-a-location-step]
Click **Create Location**
### Configure the location [#configure-the-location-step]
Configure the location settings
### Save the location [#save-the-location-step]
Save the location
## Location Types [#location-types]
| Type | Description |
self-service | Public dealership where anyone can browse and purchase vehicles (no stock limits) |
owned | Player-owned dealership with stock management, employees, direct sales and finances |
# Vehicle Images (/dealerships/vehicle-images)
Only available in Dealerships v1.2 or newer
You can now show little vehicle thumbnails in the Dealerships UI, as you can see below. This will automatically stay up to date with the latest DLC vehicles.
Vehicle thumbnails in showroom list
To enable this feature, set `Config.ShowVehicleImages = true`.
If you have addon vehicles, you can add images for these too:
1. Find the `vehicle_images` folder in the root of the script
2. Get a **.png** (has to be a png) of the addon vehicle (recommended 140x100px)
3. Drag the .png into the `vehicle_images` folder
4. Rename the file to the spawn code/model name of the vehicle - for example `adder.png`
5. Go to `fxmanifest.lua` and add `"vehicle_images/*",` into the `files` table like this:
If you're using custom vehicle images in JG Advanced Garages, you don't need to duplicate them! JG Dealerships will automatically pull those images, so long as the feature is enabled in both scripts.
# Download Purchase (/getting-started/claim-purchase)
### Check your server artifacts [#check-your-server-artifacts-step]
Double check you are not using a broken, out of date or incompatible artifact by going to [our artifact database](https://artifacts.jgscripts.com/). At an absolute minimum, our scripts require 7290, but we suggest using the recommended artifact listed in the JG Scripts Artifact DB.
### Downloading your script [#downloading-your-script-step]
To download your purchased asset, head over to the Cfx.re Portal, linked below. Log in, open the "Assets" tab, then "Granted Assets". If you have a lot of assets, you can search for `jg-`, the prefix used for all of our assets. After purchase, it may take up to 5 minutes to appear in your portal account.
### Extracting the zip [#extracting-the-zip-step]
Extract/open the zip file you just downloaded - it should end in `.pack.zip`. It should contain a folder ending in `-bundle`, such as `jg-advancedgarages-bundle`. Do **NOT** drag this folder into your server directly - open it up first. The folders inside are the resources that you can add to your server's resources folder.
### Restart your server [#restart-your-server-step]
Fully restart your FiveM server. If you just start the script via the console or txAdmin while the server is running, you will likely run into a Keymaster escrow error. A full restart ensures your server is aware of your new licence.
### Done! [#done-step]
Now that the script files are on your server, see the installation instructions for your script in the sidebar. You'll find loads of other useful info in these docs too, such as exports, guides and common errors you may run into. Have fun!
If you need more help, join our Discord: [https://discord.gg/jgscripts](https://discord.gg/jgscripts)
# FAQ (/getting-started/faq)
Immediately after purchase, your assets will be available in the [Cfx.re Portal](https://portal.cfx.re). It can take up to 5 minutes to appear. See [Download Purchase](/getting-started/claim-purchase) for a full walkthrough.
Every script has a detailed installation guide in these docs - pick your script from the sidebar or the [homepage](/) and follow its Installation page.
We offer a 7 day money-back guarantee on scripts. In some select cases, we can also offer a refund after 7 days if there is something we cannot fix. JG+ subscriptions are not eligible. Full details: [jgscripts.com/refunds](https://jgscripts.com/refunds)
You can transfer a script once through the [Cfx.re Portal](https://portal.cfx.re). The transfer must be completed within 28 days of buying or claiming the asset. After 28 days, it can no longer be transferred.
JG Scripts cannot manually move an asset to another account or reverse a completed transfer. If the original Cfx.re account is inaccessible or compromised, contact [Cfx.re Support](https://support.cfx.re/hc/en-us/requests/new). For purchase ownership issues, contact [Tebex Buyer Support](https://www.tebex.io/support-customer-form).
If you are buying for someone else, a gift package is the better option.
We don't sell fully source-available versions. Every script ships with a source-available bridge that contains all framework-related events and unencrypted function files, so you can customise how the script hooks into your server.
Our scripts are typically compatible with QBCore, Qbox & ESX Legacy (v1.3 or later). Check the script's page on the [store](https://jgscripts.com) for specifics.
Log in at [checkout.tebex.io/payment-history](https://checkout.tebex.io/payment-history/login), open the Subscriptions tab and cancel from there.
Our support team is active 7 days a week on [Discord](https://discord.gg/jgscripts) - that's the fastest route. You can also email [support@jgscripts.com](mailto:support@jgscripts.com), with replies within 3 to 5 working days.
# FiveM Escrow Errors (/getting-started/fivem-escrow-errors)
## Error Example [#error-example]
```txt
[script:jg-advancedga] Error parsing script @jg-advancedgarages/server/sv-impound.lua in resource jg-advancedgarages: @jg-advancedgarages/server/sv-impound.lua:1: syntax error near '<\1>'
[ c-scripting-core] Failed to load script server/sv-impound.lua.
```
## Solutions [#solutions]
* **You are using FileZilla and files have been corrupted during transfer** - try using an alternative FTP client such as [WinSCP](https://winscp.net/eng/index.php)
* You are transferring the folder to your server file by file - **you must upload the .zip file as-is** and then extract it **after** it has been transferred to your VPS
* Your server version is too old, the minimum version is 4752
* You can download [updated server artifacts](https://runtime.fivem.net/artifacts/fivem/build_server_windows/master/)
* Or check out the official [FiveM guide](https://docs.fivem.net/docs/server-manual/setting-up-a-server/)
## Error Example [#error-example-1]
```txt
[svadhesive] Failed to verify protected resource jg-advancedgarages
```
## Solutions [#solutions-1]
* Try restarting your server
* You are transferring the folder to your server file by file - you must upload the .zip file as-is and then extract it **after** it has been transferred to your VPS
* You don't have a `.fxap` file in the script folder - try installing the script again
* You are using FileZilla and files have been corrupted during transfer - try using an alternative FTP client such as [WinSCP](https://winscp.net/eng/index.php)
## Error Example [#error-example-2]
```txt
You lack the required entitlement to use jg-advancedgarages
```
## What does this mean? [#what-does-this-mean]
All JG Scripts use the FiveM escrow system, which means scripts are linked to your FiveM account (the account you used on Tebex).
To work, the script(s) must run on a server using a server key created by the same FiveM account you used on Tebex. You can create a server key in [FiveM Keymaster](https://keymaster.fivem.net/).
Once you've created a server key, add it to your server's `server.cfg` like this:
```txt
sv_licenseKey "5594nen725je5bw8s8rkwhahepnmsp9b"
```
## The script is on my friend's FiveM account [#the-script-is-on-my-friends-fivem-account]
To transfer the script to another account, you can head to:
[FiveM Keymaster](https://keymaster.fivem.net/) -> Purchased assets tab -> Transfer to another account
cfx.re only allow scripts to be transferred 1 time, so you won't be able to transfer the script again if you do this.
## ZAP-Hosting [#zap-hosting]
If you are using a ZAP-Hosting server, do **not** enter your server key in `server.cfg` - add it directly in your server's control panel instead. Follow the [official ZAP-Hosting instructions](https://zap-hosting.com/guides/docs/en/fivem_licensekey/) to add your server key.
Read all the instructions above **very carefully**. If you've just purchased the script and are still having issues after trying everything above, wait at least 30 minutes, as this can sometimes fix the issue. Also make sure you have done a full server restart.
# Permissions Troubleshooting (/getting-started/permissions-troubleshooting)
All admin commands in our scripts require `god` permissions in QBCore and `admin` permissions in ESX by default.
You can adjust these permissions in `framework/sv-functions.lua` -> `Functions.Server.IsAdmin()`.
1. To give someone access to the command, they need to be part of the correct group in your `server.cfg` file - see the following example:
```bash
add_principal identifier.fivem:XXXXXXX group.god # QBCore
add_principal identifier.fivem:XXXXXXX group.admin # ESX
```
2. Restart your server
3. ESX only: If you are still having issues, try running `/setgroup [id] admin` in-game
# Get Support (/getting-started/support)
Most problems are already answered in these docs - try the search (`Cmd K` / `Ctrl K`) and the [FAQ](/getting-started/faq) first, it's usually faster than waiting for a reply. Still stuck? Pick an option below.
# Translations (/getting-started/translations)
## Setting the language in our scripts [#setting-the-language-in-our-scripts]
Every script from JG Scripts is fully translatable out of the box (except Scoreboard). Head to `config.lua` and update the `Config.Locale` option. You can find a list of available translations inside the `locales` folder.
The name of the file (without the .lua extension) is the value you set `Config.Locale` to. For example, if you see a locale file called `de.lua`, update the config to `Config.Locale = "de"`.
## Contributing [#contributing]
Translations for all our scripts are handled in the translations repository on GitHub:
Before each new release, the latest translations are automatically pulled from this repository.
We would love for you to contribute! To add a new locale file, or improve an existing one, make a **Pull Request** with your changes, and one of the team will review it.
**Guidelines:**
* The locale file must be named with the language code, and be a lua file. For example `en.lua`, `fr.lua`, `de.lua` and so on.
* When creating a new locale file, copy an existing one (we recommend English, as it's the original file created by us), and replace the translations with your own.
* The start of the file must keep the variable declaration, and you must update the existing language code to match the name of the file.
* Test the file before submitting your Pull Request to ensure that there are no syntax errors.
# Updating Resources (/getting-started/updating-resources)
Don't overwrite the old resource with the new one. Files removed or renamed in the update can be left behind and cause errors.
### Read the changelog [#read-the-changelog-step]
Check the changelog for the version you're installing. Follow any SQL or extra update instructions listed there before starting your server.
### Back up your files [#back-up-your-files-step]
Make a copy of the current resource outside your server's `resources` folder. Back up your database too if the changelog includes SQL changes.
Keep the backup until you've finished testing the update.
### Download the latest version [#download-the-latest-version-step]
Download the resource again from the [Cfx.re Portal](https://portal.cfx.re/assets/granted-assets?page=1\&sort=asset.updated_at\&direction=asc). Extract the `.pack.zip`, then open the folder ending in `-bundle` to find the resource folder.
### Remove the old resource [#remove-the-old-resource-step]
Stop your server, then remove the old resource folder from your server's `resources` folder. Don't merge the new files into it.
### Add the new resource [#add-the-new-resource-step]
Move the newly downloaded resource folder into your server's `resources` folder.
The changelog will list any config options added or changed in the update. Apply those changes to the new config files, then copy across the settings you intentionally changed.
As a final check, compare the old and new config files using your code editor's diff tool or [Diffchecker](https://www.diffchecker.com/). Don't replace the new config files with your old ones.
If you edited source-available files, reapply those changes to the new files. This keeps any fixes or changes included in the update.
### Check for duplicate copies [#check-for-duplicate-copies-step]
Search your `resources` folder for the resource name. There should be one copy only. Check folders such as `[jg]`, `[standalone]` and any old bundle folders.
The resource folder must not be nested inside another folder with the same name. For example, this is incorrect: `resources/[jg]/jg-mechanic/jg-mechanic`.
### Restart and test [#restart-and-test-step]
Fully restart your server. Check the server console for errors, then test the updated resource in game before removing your backup.
# Exporting Your Changes (/handling/exporting-your-changes)
The editor lets you test handling changes in game. It does not edit files inside your server.
Use the **Export** page in the editor to copy your changes when you're ready to keep them.
## Exporting a `handling.meta` [#exporting-a-handlingmeta]
Use this method to change a vehicle's base handling in its resource.
### Test the handling [#test-the-handling-step]
Sit in the vehicle and open the editor with `/editor`. Make your changes, then use **Preview** to test them.
### Copy the XML [#copy-the-xml-step]
Select **Export** in the left sidebar, then select **handling.meta (XML)** under **Export to**.
Click **Copy to Clipboard** above the generated XML.
### Find the vehicle's `handling.meta` [#find-the-vehicles-handlingmeta-step]
Open the resource that contains the vehicle and find its `handling.meta` file. Make a backup of the file before changing it.
The editor cannot locate or update this file for you. The XML is copied to your clipboard so you can add it to the correct vehicle resource.
### Replace the handling data [#replace-the-handling-data-step]
If the `handling.meta` only contains the vehicle you edited, replace the file's contents with the copied XML.
If the file contains more than one vehicle, do not replace the whole file. Find the existing `- ` block with the same `` as the exported data. Replace that block with the exported `
- ` block and leave the other entries unchanged.
Save the file when you're done.
### Restart and test [#restart-and-test-step]
Restart the vehicle resource, delete the old vehicle and spawn it again. Confirm that the vehicle loads and the new handling is active.
## Applied handling and file changes [#applied-handling-and-file-changes]
**Apply Handling** can apply changes temporarily or store them against a plate or vehicle model in the database. None of these options update the vehicle resource's `handling.meta`.
Export and replace the `handling.meta` when you want the changes to become part of the vehicle's base handling. This also avoids storing a model-wide handling override in the database.
## JG Mechanic tuning configs [#jg-mechanic-tuning-configs]
Select **JG Mechanic Tuning Config** under **Export to** when you're creating a custom tuning option for JG Mechanic.
Choose **Overwrite** to set the listed handling values exactly. Choose **Modify** to generate the difference from the vehicle's base handling. Copy the generated Lua into the correct tuning option in `jg-mechanic/config/config.tuning.lua`.
See [Tuning](/mechanic/tuning) for the available JG Mechanic tuning options.
## Files outside the editor [#files-outside-the-editor]
JG Handling does not export or update `vehicles.meta` or `carvariations.meta`. Keep those files from the original vehicle resource and edit them separately if needed.
# Exports (/handling/exports)
There are 3 client events available. These are useful if you want to create items for either the editor or the timing tool. They are secured, so you don't need to run additional checks before using them.
```lua
-- client only
TriggerEvent("jg-handling:client:open-editor") -- opens the handling editor
TriggerEvent("jg-handling:client:open-timing-tool") -- opens the timing tool
TriggerEvent("jg-handling:client:close-timing-tool") -- closes the timing tool
```
# Installation (/handling/installation)
### Download & unzip [#download--unzip-step]
Unzip the `jg-handling-bundle` and open it.
### Add the resource to your server [#add-the-resource-to-your-server-step]
Drag the script folder (`jg-handling`) into a new folder called `[jg]` within your server's `resources` folder.
### Install dependencies [#install-dependencies-step]
Make sure you have the latest versions of [ox\_lib](https://github.com/overextended/ox_lib/releases/latest) & [oxmysql](https://github.com/overextended/oxmysql/releases/latest) installed on your server.
### Update your server.cfg [#update-your-servercfg-step]
Inside of your `server.cfg`, add a new line **after** all your other resources have started:
```txt
ensure [jg]
```
### Configure the script [#configure-the-script-step]
Inside the `config` folder, `config.lua` is the core configuration - most integrations are detected automatically, including framework (optional) and notifications. You'll mainly be adjusting the script's functionality, such as the job lock and timing tool features.
The editor does not change files inside your vehicle resources. See [Exporting Your Changes](/handling/exporting-your-changes) when you're ready to update a vehicle's `handling.meta` or create a JG Mechanic tuning config.
## Troubleshooting [#troubleshooting]
JG Handling tries to automatically make the required database changes. In some cases, this automatic installation fails and you need to make the changes manually.
Head into the `install` folder within `jg-handling`. Run the `database.sql` file within your database software (such as PhpMyAdmin or HeidiSQL).
If you get an error along the lines of `cannot use the syntax IF NOT EXISTS`, remove every instance of `IF NOT EXISTS` from the `database.sql` file and re-run it. It will still run fine.
Make sure you're running this SQL code within the correct database - triple-check and cross-reference the name of the database!
# Languages (/handling/languages)
{/* Generated by scripts/update-languages.mjs from the jgscripts/translations
repository - do not edit by hand, changes will be overwritten. */}
JG Handling is available in 9 languages, translated by the community.
## Setting the language [#setting-the-language]
In `config.lua`, set `Config.Locale` to the code of the language you want, then restart the resource:
```lua
Config.Locale = "en"
```
## Available languages [#available-languages]
| Language | `Config.Locale` value |
| --------------------- | --------------------- |
| English | `en` |
| Arabic | `ar` |
| Chinese (Traditional) | `zh-tw` |
| Czech | `cs` |
| French | `fr` |
| German | `de` |
| Portuguese | `pt` |
| Swedish | `sv` |
| Turkish | `tr` |
## Contributing a translation [#contributing-a-translation]
Spotted a mistake, or want the script in your language? Translations live in our public [translations repository](https://github.com/jgscripts/translations), and anyone can contribute. See the [Translations](/getting-started/translations) guide for how to submit yours.
# Change Server Logo (/hud/change-server-logo)
JG HUD supports displaying your server's logo, if you choose to enable it. We've included an example logo named `server-logo.png` in the root of the `jg-hud` folder. To use your own, replace this file.
The image must be named exactly `server-logo` and be in the `.png` format.
The optimal image size is 200x120px. We recommend resizing your logo to match, although images of any size will be adjusted automatically.
# Custom Street/Zone Names (/hud/custom-street-zone-names)
Available in v1.2 or newer
JG HUD lets you rename any street or zone (area) in the game. This is useful for immersion if your server isn't based in Los Santos.
Make all of the following changes in `config/config.data.lua`.
## Adding Custom Street Names [#adding-custom-street-names]
1. Look for `Config.CustomStreetNames`
2. To add a new entry, you need the **hash** of the street you'd like to update. This is quite difficult to find, so we made a file to make things easier. You can find it here: [https://github.com/jgscripts/gtav-street-zone-hashes/blob/main/streets.txt](https://github.com/jgscripts/gtav-street-zone-hashes/blob/main/streets.txt)
3. Search in the file above for the street you'd like to update, for example, *Los Santos Freeway*. Copy the hash (starts with `0x`) to the left of the street name. In this case, it will be `0xAC9F694E`.
4. Add it to the table, with square brackets around the hash. Do NOT add quotes around the hash. Then set it equal to the name you'd like to change it to. This is what it should look like:
```lua
Config.CustomStreetNames = {
[0xAC9F694E] = "Custom name for Los Santos Freeway",
}
```
## Adding Custom Zone Names [#adding-custom-zone-names]
1. Look for `Config.CustomZoneNames`
2. To add a new entry, you need the **hash** of the zone you'd like to update. This is quite difficult to find, so we made a file to make things easier. You can find it here: [https://github.com/jgscripts/gtav-street-zone-hashes/blob/main/zones.txt](https://github.com/jgscripts/gtav-street-zone-hashes/blob/main/zones.txt)
3. Search in the file above for the zone/area you'd like to update, for example, *Los Santos International Airport*. Copy the hash (which is a shorter, capitalised version of the zone) to the left of the zone name. In this case, it will be `AIRP`.
4. Add it to the table by setting the hash equal to the name you'd like to change it to. This is what it should look like:
```lua
Config.CustomZoneNames = {
AIRP = "Custom name for LS International Airport",
}
```
## Using Your Changes Game-Wide [#using-your-changes-game-wide]
By default, these custom names will only apply to the compass within JG HUD. If you want your custom names to apply across the entire game, so both the base game and other scripts use them, set `Config.CustomNamesShouldUpdateGameTextEntries = true`. JG HUD will apply all the text entries for you automatically.
# Default Settings (/hud/default-settings)
It's best to do this **before** you launch JG HUD within your server. If you've allowed users to edit their own settings in the past, those settings will take precedence over your new default settings profile.
JG HUD lets you set default settings for **all users**. These can include anything users can usually adjust, including colours, visibility of components, size, position and more.
### Creating & exporting a default settings profile [#creating--exporting-a-default-settings-profile-step]
To create a default settings profile, go into `/settings` in game as you usually would to adjust your own HUD.
Once you're happy with the settings you've adjusted, you need to **export them to JSON**. Click the "Import & Export" tab on the left-hand side.

Once in this tab, click "Copy to Clipboard" to copy the configuration JSON.
### Making the settings profile your server default [#making-the-settings-profile-your-server-default-step]
Now that you've got the JSON on your clipboard, you need to add it to a file within the resource's code. We've created one for you inside the `data` folder, called `default-settings.json` (full path: `jg-hud/data/default-settings.json`).
You can also make your own file wherever you'd like (within the `jg-hud` resource folder), and point to its location using the config option `Config.DefaultSettingsData`.
Once you've got the correct file, open it in a text editor and paste in the JSON from your clipboard. Like this:
### Important additional info and settings [#important-additional-info-and-settings-step]
This will now be the default profile for all **new users** of JG HUD in your server. For existing users, their current settings/layout will take precedence.
It's therefore best to set this up before players join your server and use JG HUD for the first time.
If you want players to always use the exact configuration you've made for them, set both of the following config options to false. Players will then always see whatever is in the default settings JSON file. Preventing users from changing their settings, especially the layout of the HUD, is **not recommended**. Some people are on a larger screen and may want to make certain components larger and easier to see.
```lua
Config.AllowPlayersToEditSettings = false
Config.AllowUsersToEditLayout = false
```
If you're allowing users to change their settings/layout, they can reset to your new defaults via the "Reset to Default" tab on the left of the settings panel. The defaults will come from the file you've set up.
Have fun configuring!
# Editing the HUD (/hud/editing-the-hud)
Most configuration is done in-game, via the HUD settings. By default, this is opened with the `/settings` command. You can change this command in the config via `Config.OpenSettingsCommand`.
The settings panel should be fairly intuitive. Click the options on the left to adjust different parts of the HUD. The necessary parts of the HUD will show if hidden, so you can edit them.
You can change the layout by clicking the button that's always in the top right of the settings panel.
By default, players can change the HUD for just their eyes. If you want to enforce a custom HUD layout & setup for all your players, see [Default Settings](/hud/default-settings).
# Exports (/hud/exports)
## toggleHud [#togglehud]
Hide or show the entire HUD.
```lua
-- client only
-- show: boolean - true to show the HUD, false to hide it
exports["jg-hud"]:toggleHud(show)
-- example: hide the HUD during a cutscene
exports["jg-hud"]:toggleHud(false)
```
## toggleVehicleControl [#togglevehiclecontrol]
Hide or show the vehicle control menu.
```lua
-- client only
-- show: boolean - true to show the menu, false to hide it
exports["jg-hud"]:toggleVehicleControl(show)
```
# Installation (/hud/installation)
### Download & unzip [#download--unzip-step]
Unzip the `jg-hud-bundle` and open the `jg-hud-bundle` folder.
### Add the resource to your server [#add-the-resource-to-your-server-step]
Drag the script folder within (`jg-hud`) into a new folder called `[jg]` within your server's `resources` folder.
### Install ox\_lib [#install-ox_lib-step]
Make sure you have the latest version of [ox\_lib](https://github.com/overextended/ox_lib/releases/latest) installed on your server. This script does not use a database, so oxmysql is *not* required.
### Update your server.cfg [#update-your-servercfg-step]
Inside of your `server.cfg`, add a new line **after** all your other resources have started:
```txt
ensure [jg]
```
### Configure the script [#configure-the-script-step]
There are some basic options to adjust in `config/config.lua` - mainly integrations, guard rails and essential options to get things up and running. Built-in datasets such as speed limits, GTA V HUD component hiding and train stations live in `config/config.data.lua`.
Nearest postal data is in `data/nearest-postal` - full credits to BlockBa5her/DevBlocky, whose original repo is [on GitHub](https://github.com/DevBlocky/nearest-postal).
Everything else is customised in-game - see [Editing the HUD](/hud/editing-the-hud) next.
## Troubleshooting [#troubleshooting]
A memory leak in older versions of `xsound` can cause FiveM's browser-based interfaces to run out of memory. When this happens, JG HUD can disappear alongside inventory, phone and other UI images.
1. Stop your server.
2. Replace your current `xsound` resource with the [xSound 1.1 experimental build](https://github.com/Xogy/xsound/releases/tag/1.1).
3. Fully restart your server.
4. Recreate the conditions that caused the images to disappear. If the HUD and other images remain visible, the `xsound` memory leak was the cause.
# Languages (/hud/languages)
{/* Generated by scripts/update-languages.mjs from the jgscripts/translations
repository - do not edit by hand, changes will be overwritten. */}
JG HUD is available in 8 languages, translated by the community.
## Setting the language [#setting-the-language]
In `config.lua`, set `Config.Locale` to the code of the language you want, then restart the resource:
```lua
Config.Locale = "en"
```
## Available languages [#available-languages]
| Language | `Config.Locale` value |
| --------------------- | --------------------- |
| English | `en` |
| Chinese (Traditional) | `zh-tw` |
| Czech | `cs` |
| French | `fr` |
| German | `de` |
| Portuguese | `pt` |
| Swedish | `sv` |
| Turkish | `tr` |
## Contributing a translation [#contributing-a-translation]
Spotted a mistake, or want the script in your language? Translations live in our public [translations repository](https://github.com/jgscripts/translations), and anyone can contribute. See the [Translations](/getting-started/translations) guide for how to submit yours.
# Creating Locations (/mechanic/creating-locations)
`Config.MechanicLocations` contains every mechanic business on your server. Each named entry, such as `bennys` or `lscustoms`, is one mechanic. The `locations` table inside that entry contains the places where players can interact with it.
All interaction points in the same `locations` table share the mechanic's job, prices, tuning options, shops and stashes.
## Choose what to add [#choose-what-to-add]
| What you need | What to change |
| --------------------------------------------------------- | ------------------------------------------------------------- |
| A separate mechanic business with its own job or settings | Duplicate a complete entry inside `Config.MechanicLocations` |
| Another interaction point for an existing mechanic | Add an entry to that mechanic's `locations` table |
| A mechanic operated by an employee job | Duplicate the `lscustoms` entry and keep `type = "owned"` |
| A mechanic anyone can use | Duplicate the `bennys` entry and keep `type = "self-service"` |
Back up `config/config.lua` before editing it. A missing comma or brace will stop the config from loading.
## Add a mechanic [#add-a-mechanic]
### Find the mechanic table [#find-the-mechanic-table-step]
Open `config/config.lua` and search for this line:
```lua title="config/config.lua"
Config.MechanicLocations = {
```
Don't use line numbers. They change between releases.
The table ends at the matching `}` immediately before the electric vehicle section. Every mechanic entry must stay between the opening and closing braces of `Config.MechanicLocations`.
### Copy a matching mechanic [#copy-a-matching-mechanic-step]
Choose an existing entry with the type you need:
* For an owned mechanic, copy from `lscustoms = {` to the brace that closes the complete `lscustoms` entry.
* For a self-service mechanic, copy from `bennys = {` to the brace that closes the complete `bennys` entry.
Copy the whole entry, including its `locations`, `blip`, `mods`, `tuning`, `carLifts`, `shops` and `stashes` settings where present. Don't copy the outer `Config.MechanicLocations = {` line or its final closing brace.
### Paste the duplicate inside the table [#paste-the-duplicate-inside-the-table-step]
Paste the copied entry after an existing mechanic and before the final brace of `Config.MechanicLocations`.
Mechanic entries must be separated by a comma. Change the brace that closes the previous mechanic from `}` to `},` before adding another entry.
This reduced example shows the complete table boundary and comma positions. Keep the other settings from the entry you copied:
```lua title="config/config.lua"
Config.MechanicLocations = {
lscustoms = {
type = "owned",
job = "mechanic",
locations = {
{
coords = vector3(-337.25, -137.2, 38.35),
size = 6.5,
showBlip = true,
},
},
},
harmony = {
type = "owned",
job = "harmony",
locations = {
{
coords = vector3(1177.62, 2640.83, 37.75),
size = 6.5,
showBlip = true,
},
{
coords = vector3(1182.28, 2637.91, 37.75),
size = 3.0,
showBlip = false,
employeeOnly = true,
},
},
},
}
```
The first `},` closes `lscustoms` and separates it from `harmony`. The final `}` closes `Config.MechanicLocations`. If you paste the new mechanic after that final brace, it won't be part of the table.
### Change the duplicate's settings [#change-the-duplicates-settings-step]
Rename the copied entry and update its settings. This is a complete owned mechanic entry that can be pasted inside `Config.MechanicLocations`:
```lua title="config/config.lua"
harmony = {
type = "owned",
job = "harmony",
jobManagementRanks = {4},
logo = "ls_customs.png",
commission = 0,
locations = {
{
coords = vector3(1177.62, 2640.83, 37.75),
size = 6.5,
showBlip = true,
},
{
coords = vector3(1182.28, 2637.91, 37.75),
size = 3.0,
showBlip = false,
employeeOnly = true,
},
},
blip = {
id = 446,
color = 47,
scale = 0.7,
},
mods = {
repair = { enabled = true, price = 500, percentVehVal = 0.01 },
performance = { enabled = true, price = 500, percentVehVal = 0.01, priceMult = 0.1 },
cosmetics = { enabled = true, price = 500, percentVehVal = 0.01, priceMult = 0.1 },
stance = { enabled = true, price = 500, percentVehVal = 0.01 },
respray = { enabled = true, price = 500, percentVehVal = 0.01 },
wheels = { enabled = true, price = 500, percentVehVal = 0.01, priceMult = 0.1 },
neonLights = { enabled = true, price = 500, percentVehVal = 0.01 },
headlights = { enabled = true, price = 500, percentVehVal = 0.01 },
tyreSmoke = { enabled = true, price = 500, percentVehVal = 0.01 },
bulletproofTyres = { enabled = true, price = 500, percentVehVal = 0.01 },
extras = { enabled = true, price = 500, percentVehVal = 0.01 },
},
tuning = {
engineSwaps = { enabled = true, requiresItem = true },
drivetrains = { enabled = true, requiresItem = true },
turbocharging = { enabled = true, requiresItem = true },
tyres = { enabled = true, requiresItem = true },
brakes = { enabled = true, requiresItem = true },
driftTuning = { enabled = true, requiresItem = true },
gearboxes = { enabled = true, requiresItem = true },
},
carLifts = {},
shops = {},
stashes = {},
},
```
Change these values for your server:
* `harmony` is the unique config name. Use letters, numbers and underscores. Don't use the same name as another mechanic.
* `type` must be `"owned"` or `"self-service"`.
* `job` must exactly match the job name in your framework. It only applies to an owned mechanic.
* `jobManagementRanks` contains the job grades that can use management features.
* `logo` must match a file in the resource's `logos` folder.
* `commission` is the percentage paid to the employee. `0` disables commission.
* `carLifts`, `shops` and `stashes` are empty in this example. Copy and edit the matching sections from `lscustoms` if you need them.
Don't leave copied `carLifts`, `shops` or `stashes` at the old mechanic's coordinates. Change every coordinate or replace the unused section with an empty table, as shown above.
### Restart and test [#restart-and-test-step]
Save `config/config.lua`, then restart the resource from the server console:
```txt
restart jg-mechanic
```
Check the server console for a Lua error. Then visit every configured interaction point and confirm:
* the interaction zone appears in the correct place
* the blip only appears where `showBlip = true`
* employees with the configured job can use an owned mechanic
* players without that job can't use employee-only interaction points
## Add interaction points to one mechanic [#add-interaction-points-to-one-mechanic]
Add another entry inside the existing mechanic's `locations` table when both points should use the same mechanic business and settings:
```lua title="config/config.lua"
locations = {
{
coords = vector3(-337.25, -137.2, 38.35),
size = 6.5,
showBlip = true,
},
{
coords = vector3(-324.2, -132.0, 38.54),
size = 3.0,
showBlip = false,
employeeOnly = true,
},
},
```
The comma after the first interaction entry is required because another entry follows it. The trailing comma after the second entry is valid and makes it safer to add another one later.
Each `vector3` must contain exactly three values in this order: X, Y and Z.
```lua title="config/config.lua"
coords = vector3(1177.62, 2640.83, 37.75)
```
Use the player's position coordinates. Don't add a heading value and don't change `vector3` to `vector4` for a mechanic interaction point.
| Setting | What it controls |
| -------------- | ----------------------------------------------- |
| `coords` | Centre of the interaction zone |
| `size` | Width, depth and height of the interaction zone |
| `showBlip` | Whether this point creates a map blip |
| `employeeOnly` | Whether only employees can use this point |
## Troubleshooting [#troubleshooting]
The new entry is outside `Config.MechanicLocations`, or a comma or brace is missing.
Check that the new mechanic is before the final brace of `Config.MechanicLocations`. Each mechanic entry and each interaction entry must be separated from the next one with a comma. Compare the indentation and closing braces with the examples above.
Check that `coords` uses `vector3` with three numbers and that the entry is inside the mechanic's `locations` table. Increase `size` while testing if the interaction area is too small to find.
If the point has `employeeOnly = true`, test while using the job configured in the mechanic's `job` setting.
The interaction entry was added to the wrong mechanic's `locations` table. Move the complete entry, from its opening `{` to its closing `},`, into the correct mechanic entry.
The mechanic's `job` value must exactly match the framework job name, including capitalisation. Confirm the job exists and that the player has that job, then reconnect or reload the player's job after changing framework data.
# Debug & Event Logging (/mechanic/debug-and-event-logging)
Available in v1.7.0 and newer.
JG Mechanic now uses a central logging system. You only need to edit:
* `config/config.lua` to choose where each logging category is sent.
* `config/config.webhooks.lua` to add Discord webhook URLs.
## Logging Categories [#logging-categories]
Logging is split into three core routes:
| Route | Used for |
| ---------- | ---------------------------------------------------------------------------------------------- |
| `LOG` | Debug logs (can keep this off unless running into issues or asked to by support) |
| `EVENT` | Normal audit logs, such as invoices, orders, tuning, shops, repairs, nitrous, and duty changes |
| `SECURITY` | Denied actions, invalid values, suspicious requests, and vehicle data warnings |
## Configure Destinations [#configure-destinations]
Set destinations in `config/config.lua`:
```lua
Config.Logs = {
LOG = {
enabled = true,
destinations = { "console" }
},
EVENT = {
enabled = true,
destinations = { "ox_lib" }
},
SECURITY = {
enabled = true,
destinations = { "console", "ox_lib" }
}
}
```
Supported destinations are:
| Destination | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `console` | Prints to the server console |
| `ox_lib` | Sends to lib.logger
Learn more: ox\_lib logger documentation
|
| `discord` | Sends to Discord webhooks from config.webhooks.lua
(Not recommended)
|
To disable a route:
```lua
Config.Logs = {
...
LOG = {
enabled = false,
destinations = ...
}
}
```
## Common Examples [#common-examples]
Use `ox_lib` for normal events and security logs:
```lua
Config.Logs = {
LOG = {
enabled = true,
destinations = { "console" }
},
EVENT = {
enabled = true,
destinations = { "ox_lib" }
},
SECURITY = {
enabled = true,
destinations = { "ox_lib" }
}
}
```
Use Discord for events, but keep security warnings in `console` and `ox_lib`:
```lua
Config.Logs = {
LOG = {
enabled = true,
destinations = { "console" }
},
EVENT = {
enabled = true,
destinations = { "discord" }
},
SECURITY = {
enabled = true,
destinations = { "console", "ox_lib" }
}
}
```
Send events to both Discord and `ox_lib`:
```lua
Config.Logs = {
LOG = {
enabled = true,
destinations = { "console" }
},
EVENT = {
enabled = true,
destinations = { "discord", "ox_lib" }
},
SECURITY = {
enabled = true,
destinations = { "console", "ox_lib" }
}
}
```
Disable debug output:
```lua
Config.Logs = {
LOG = {
enabled = false,
destinations = { "console" }
},
EVENT = {
enabled = true,
destinations = { "ox_lib" }
},
SECURITY = {
enabled = true,
destinations = { "console", "ox_lib" }
}
}
```
Visual debug zone markers are separate from logging:
```lua
Config.DebugZones = false
```
## Discord Webhook Setup [#discord-webhook-setup]
Discord webhook URLs are configured in `config/config.webhooks.lua`.
Only add webhook URLs here. Don't put them in `config/config.lua`, because `config.lua` is shared with the client.
```lua
Webhooks = {}
Webhooks.SelfService = ""
Webhooks.Orders = ""
Webhooks.TabletTuning = ""
Webhooks.Servicing = ""
Webhooks.Invoices = ""
Webhooks.Mechanic = ""
Webhooks.Admin = ""
Webhooks.Shop = ""
Webhooks.Nitrous = ""
Webhooks.Repair = ""
Webhooks.Duty = ""
Webhooks.VehicleData = ""
Webhooks.Security = ""
```
Discord logs are only sent when the route destination includes `"discord"`.
If a Discord URL is empty, that Discord log is skipped. Other destinations, such as `console` or `ox_lib`, will still work.
## Migrating Discord Webhooks From `server/sv-webhooks.lua` [#migrating-discord-webhooks-from-serversv-webhookslua]
1. Open your old `server/sv-webhooks.lua`.
2. Copy each old webhook URL into the matching entry in `config/config.webhooks.lua`.
3. Set your route destinations in `config/config.lua`.
4. Add `"discord"` to any route that should send Discord webhook logs.
5. Leave new webhook categories blank unless you want logs for those areas.
Old categories are still available:
| Old webhook | New config entry |
| ----------------------- | ----------------------- |
| `Webhooks.SelfService` | `Webhooks.SelfService` |
| `Webhooks.Orders` | `Webhooks.Orders` |
| `Webhooks.TabletTuning` | `Webhooks.TabletTuning` |
| `Webhooks.Servicing` | `Webhooks.Servicing` |
| `Webhooks.Invoices` | `Webhooks.Invoices` |
| `Webhooks.Mechanic` | `Webhooks.Mechanic` |
| `Webhooks.Admin` | `Webhooks.Admin` |
New optional categories are:
| New webhook | Used for |
| ---------------------- | ----------------------------------- |
| `Webhooks.Shop` | Mechanic shop item purchases |
| `Webhooks.Nitrous` | Nitrous bottle installs and refills |
| `Webhooks.Repair` | Self-service repair purchases |
| `Webhooks.Duty` | Mechanic duty toggles |
| `Webhooks.VehicleData` | Vehicle data and statebag warnings |
| `Webhooks.Security` | General security warnings |
## Discord Appearance [#discord-appearance]
You can also edit the Discord username, avatar, and embed colours in `config/config.webhooks.lua`:
```lua
Config.Logs.Discord = {
username = "JG Mechanic Webhook",
avatarUrl = "https://example.com/avatar.png",
webhooks = Webhooks,
colors = {
success = 0x2ecc71,
danger = 0xe74c3c,
warning = 0xf1c40f,
debug = 0x3498db,
default = 0xff6700
}
}
```
# Installation (/mechanic/installation)
### Download & unzip [#download--unzip-step]
Unzip the `jg-mechanic-bundle` and open it - the folders inside are the resources for your server.
### Add the resources to your server [#add-the-resources-to-your-server-step]
Drag all 4 folders (`jg-mechanic`, `jg-mechanic-props`, `jg-vehiclemileage` and `jg-textui`) into a new folder called `[jg]` within your server's `resources` folder.
If you don't plan on using our new default Text UI, you don't need to transfer it to your server. You can choose a different Text UI script in the config.
### Update your server.cfg [#update-your-servercfg-step]
In your `server.cfg`, add a new line **after** all your other resources have started:
```txt
ensure [jg]
```
### Set up inventory items [#set-up-inventory-items-step]
Navigate into the `install` folder, and then into the `inventory` folder. Follow the guide for your chosen inventory - the item images are in the `images` folder.
* [ox\_inventory](/mechanic/inventory-setup/ox_inventory)
* [qb-inventory](/mechanic/inventory-setup/qb-inventory)
* [esx\_inventory](/mechanic/inventory-setup/esx_inventory)
### Configure the script [#configure-the-script-step]
Inside the `config` folder, `config.lua` is the core configuration - most integrations (framework, text UI, notifications and more) are automatically detected. The main things to customise are mechanic locations, core pricing, servicing and tuning options.
The 3 other config files fine-tune how cosmetics, servicing & tuning work in your city. They are heavily commented, but can be a little complicated - only modify them if you're comfortable with Lua. See [Pricing](/mechanic/pricing), [Servicing](/mechanic/servicing) and [Tuning](/mechanic/tuning) before changing those files.
### Set up jobs and banking [#set-up-jobs-and-banking-step]
Follow [Job Setup](/mechanic/job-setup) for every owned mechanic location.
If `Config.UseFrameworkJobs = true`, set up employee management through your framework's boss menu or a compatible third-party boss menu. JG Mechanic doesn't show its built-in Management app when you use framework jobs.
If `Config.UseFrameworkJobs = true`, [create a society bank account](/mechanic/job-setup/society-banking) for every unique mechanic job before taking work orders. JG Mechanic doesn't create accounts in third-party banking resources.
## Video Installation Guide [#video-installation-guide]
Scorpion from the JG Scripts community has created a detailed installation guide, including configuration & inventory setup for QB, Qbox & ESX.
# Integrations (/mechanic/integrations)
## Key [#key]
## Inventories [#inventories]
| Script | Status | Notes |
| esx_inventory | ✅ Officially Supported | Config.Inventory = "esx_inventory" |
| qb-inventory | ✅ Officially Supported | Config.Inventory = "qb-inventory" |
| ox_inventory | ✅ Officially Supported | Config.Inventory = "ox_inventory" |
| qs-inventory | ☑️ Supported | Config.Inventory = "qs-inventory" |
| codem-inventory | ☑️ Supported | Config.Inventory = "codem-inventory" |
Using an unsupported inventory? Read the [custom inventory system guide](/mechanic/inventory-setup/custom-inventory-system).
## Notifications [#notifications]
| Script | Status | Notes |
| jg-textui | ✅ Officially Supported | Config.Notifications = "default" |
| ox_lib | ✅ Officially Supported | Config.Notifications = "ox_lib" |
| ps-ui | ✅ Officially Supported | Config.Notifications = "ps-ui" |
| lation_ui | ☑️ Supported | Config.Notifications = "lation_ui" |
| okokNotify | ☑️ Supported | Config.Notifications = "okokNotify" |
| nox_notify | ☑️ Supported | Config.Notifications = "nox_notify" |
## Banks [#banks]
| Script | Status | Notes |
| okokBanking | ✅ Officially Supported | Config.SocietyBanking = "okokBanking" |
| fd_banking | ✅ Officially Supported | Config.SocietyBanking = "fd_banking" |
| Renewed-Banking | ✅ Officially Supported | Config.SocietyBanking = "Renewed-Banking" |
| qb-banking | ✅ Officially Supported | Config.SocietyBanking = "qb-banking" |
| qb-management | ✅ Officially Supported | Config.SocietyBanking = "qb-management" |
| esx_addonaccount | ✅ Officially Supported | Config.SocietyBanking = "esx_addonaccount" |
| tgg-banking | ☑️ Supported | Config.SocietyBanking = "tgg-banking" |
| wasabi_banking | ☑️ Supported | Config.SocietyBanking = "wasabi_banking" |
## Progress Bars [#progress-bars]
| Script | Status | Notes |
| -------------------------------------------------------------------- | ------------ | --------------------------------- |
| [ox\_lib](https://github.com/overextended/ox_lib/releases/) (circle) | ☑️ Supported | Config.ProgressBar = "ox-circle" |
| [ox\_lib](https://github.com/overextended/ox_lib/releases/) (bar) | ☑️ Supported | Config.ProgressBar = "ox-bar" |
| lation\_ui | ☑️ Supported | Config.ProgressBar = "lation\_ui" |
| qb progressbar | ☑️ Supported | Config.ProgressBar = "qb" |
## Skill Checks [#skill-checks]
| Script | Status | Notes |
| ----------------------------------------------------------- | ------------ | -------------------------------- |
| [ox\_lib](https://github.com/overextended/ox_lib/releases/) | ☑️ Supported | Config.SkillCheck = "ox" |
| qb (ps-ui circle) | ☑️ Supported | Config.SkillCheck = "qb" |
| lation\_ui | ☑️ Supported | Config.SkillCheck = "lation\_ui" |
## Text UI [#text-ui]
| Script | Status | Notes |
| ------------------------------------------------------------ | ---------------------- | ------------------------------ |
| [jg-textui](https://github.com/jgscripts/jg-textui) | ✅ Officially Supported | Config.DrawText = "jg-textui" |
| [ox\_lib](https://github.com/overextended/ox_lib/releases/) | ☑️ Supported | Config.DrawText = "ox\_lib" |
| [okokTextUI](https://docs.okokscripts.io/scripts/okoktextui) | ☑️ Supported | Config.DrawText = "okokTextUI" |
| [ps-ui](https://github.com/Project-Sloth/ps-ui) | ☑️ Supported | Config.DrawText = "ps-ui" |
| lation\_ui | ☑️ Supported | Config.DrawText = "lation\_ui" |
| qb | ☑️ Supported | Config.DrawText = "qb" |
## Targets [#targets]
Targets are used for shops and stashes only.
| Script | Status | Notes |
| ---------------------------------------------------------------- | ------------ | ---------------------------- |
| [ox\_target](https://github.com/overextended/ox_target/releases) | ☑️ Supported | Config.Target = "ox\_target" |
| [qb-target](https://github.com/qbcore-framework/qb-target) | ☑️ Supported | Config.Target = "qb-target" |
# Inventory Items (/mechanic/inventory-items)
JG Mechanic includes many new items. Some are used as part of installation processes in the tablet, others are usable items. Either way, we leave it up to you how you want to distribute these items to players - **you could set up shops, stashes, or use other methods** so that mechanics or general civilians can access them.
Below are all the new items available as part of the install process. The "Name" is the internal game name and can be used to spawn the item in (such as via `/giveitem` in QBCore). The label is the 'visual name' you'll see more often throughout the UIs.
| Name | Label | Notes |
 | engine_oil | Engine Oil | Servicing item |
 | tyre_replacement | Tyre Replacement | Servicing item |
 | clutch_replacement | Clutch Replacement | Servicing item |
 | air_filter | Air Filter | Servicing item |
 | spark_plug | Spark Plug | Servicing item |
 | suspension_parts | Suspension Parts | Servicing item |
 | brakepad_replacement | Brakepad Replacement | Servicing item |
 | i4_engine | I4 Engine | Engine Swaps |
 | v6_engine | V6 Engine | Engine Swaps |
 | v8_engine | V8 Engine | Engine Swaps |
 | v12_engine | V12 Engine | Engine Swaps |
 | turbocharger | Turbocharger | Custom Tuning Parts |
 | ev_motor | EV Motor | Servicing Item |
 | ev_battery | EV Battery | Servicing Item |
 | ev_coolant | EV Coolant | Servicing Item |
 | awd_drivetrain | AWD Drivetrain | Custom Tuning Parts |
 | rwd_drivetrain | RWD Drivetrain | Custom Tuning Parts |
 | fwd_drivetrain | FWD Drivetrain | Custom Tuning Parts |
 | slick_tyres | Slick Tyres | Custom Tuning Parts |
 | semi_slick_tyres | Semi-slick Tyres | Custom Tuning Parts |
 | offroad_tyres | Offroad Tyres | Custom Tuning Parts |
 | ceramic_brakes | Ceramic Brakes | Custom Tuning Parts |
 | drift_tuning_kit | Drift Tuning Kit | Custom Tuning Parts |
 | lighting_controller | Lighting Controller | Usable Item & for applying orders |
 | stancing_kit | Stancing Kit | Usable Item & for applying orders |
 | cosmetic_part | Body Kit | For applying orders |
 | respray_kit | Respray Kit | For applying orders |
 | vehicle_wheels | Vehicle Wheels | For applying orders |
 | tyre_smoke_kit | Tyre Smoke Kit | For applying orders |
 | bulletproof_tyres | Bulletproof Tyres | For applying orders |
 | extras_kit | Extras Kit | For applying orders |
 | nitrous_bottle | Nitrous Bottle | NOS Refill |
 | empty_nitrous_bottle | Empty Nitrous Bottle | Unused currently |
 | nitrous_install_kit | Nitrous Install Kit | NOS Installation |
 | cleaning_kit | Cleaning Kit | Usable Item |
 | repair_kit | Repair Kit | Usable Item |
 | duct_tape | Duct Tape | Usable Item |
 | performance_part | Performance Part | For applying orders |
 | mechanic_tablet | Mechanic Tablet | Usable Item (can also be used via /tablet) |
# Languages (/mechanic/languages)
{/* Generated by scripts/update-languages.mjs from the jgscripts/translations
repository - do not edit by hand, changes will be overwritten. */}
JG Mechanic is available in 13 languages, translated by the community.
## Setting the language [#setting-the-language]
In `config.lua`, set `Config.Locale` to the code of the language you want, then restart the resource:
```lua
Config.Locale = "en"
```
## Available languages [#available-languages]
| Language | `Config.Locale` value |
| --------------------- | --------------------- |
| English | `en` |
| Arabic | `ar` |
| Chinese (Simplified) | `cn` |
| Chinese (Traditional) | `zh-tw` |
| Czech | `cs` |
| French | `fr` |
| German | `de` |
| Hungarian | `hu` |
| Italian | `it` |
| Japanese | `ja` |
| Portuguese | `pt` |
| Spanish | `es` |
| Swedish | `sv` |
## Contributing a translation [#contributing-a-translation]
Spotted a mistake, or want the script in your language? Translations live in our public [translations repository](https://github.com/jgscripts/translations), and anyone can contribute. See the [Translations](/getting-started/translations) guide for how to submit yours.
# Manual Transmissions & Smooth First Gear (/mechanic/manual-transmissions-and-smooth-first-gear)
Requires **game build 3095** or newer. You can enforce this on your server by adding `sv_enforceGameBuild 3095` to your `server.cfg`.
Available in Mechanic v1.5 and newer. These settings won't appear or have any effect on electric vehicles.
## Setup [#setup]
This feature relies on some new-ish game functionality, via `strAdvancedFlags` in a vehicle's `handling.meta`. For everything to work as expected, this property at least needs to be present in the file.
### Base Game Vehicles [#base-game-vehicles]
Most base game vehicles already have it, but annoyingly, many don't. To fix this, you need to re-stream the `handling.meta` for all base game vehicles. Sounds complicated, but we made a resource for it - drag and drop it, then continue with this guide.
### Addon Vehicles [#addon-vehicles]
If you're trying to get this to work with an addon vehicle, you'll unfortunately have to mess with some code. Head into the vehicle's files, and locate the `handling.meta`. Within there, you'll need to add:
```xml
0
```
to the section `CCarHandlingData`. Just like this:
If the section `- ` isn't present, you'll have to add it yourself.
## Manual Transmission [#manual-transmission]
You can now add a *real* manual transmission in the tablet's tuning parts menu. This is a special item that will change the vehicle's `strAdvancedFlags` to prevent automatic shifting and require the player to do it themselves. If you shift gears badly, the engine will take damage. The experience is fairly realistic, without being too difficult for the average player. The car won't move if you try to set off in 4th gear, for example.
To make it as easy as possible to use and actually desirable on your server, we detect if a vehicle has been stuck in a gear at high RPM for an extended time, and show a prompt with the key bindings to upshift/downshift.
This functionality is only available in **game build 3095 or newer**. To enforce a game build on your server, add `sv_enforceGameBuild 3095` to your `server.cfg`.
See it in action here:
## Changing Key Binds [#changing-key-binds]
This is done in-game. Hit `ESC` to go into the pause menu, then head to Settings > Key Binds > GTA Online Vehicles and adjust the "Shift Gear Up"/"Shift Gear Down" binds.
## Smooth First Gear [#smooth-first-gear]
This is a global config option that applies server-wide to all vehicles, including vehicles just spawned in/not owned. It will also update the vehicle's `strAdvancedFlags` to slow the RPM curve of the first gear to a much more realistic rate. This gives a console-type effect on keyboard, with reduced wheelspin and more time to upshift without being in the redline when using a manual transmission.
Enable it by setting:
```lua
Config.SmoothFirstGear = true
```
# Pricing (/mechanic/pricing)
JG Mechanic uses different settings for standard vehicle mods, custom tuning parts and mechanic shop items.
| What you're pricing | Where to change it |
| ------------------------------------------------ | ---------------------------------------------------------------- |
| Standard performance mods, cosmetics and repairs | Each location's `mods` table in `config/config.lua` |
| Custom tuning parts, such as engine swaps | `Config.Tuning` in `config/config.tuning.lua` |
| Items sold in mechanic shops | Each shop's `items` table in `config/config.lua` |
| Customer invoices | Entered by the mechanic in the tablet |
| Employee commission | The owned location's `commission` setting in `config/config.lua` |
Standard mod and shop prices are configured separately for each mechanic location. Update every location if you want those prices to match. Custom tuning option prices are shared across all locations.
## Standard mods and repairs [#standard-mods-and-repairs]
The `mods` table inside each entry in `Config.MechanicLocations` controls standard GTA upgrades:
```lua title="config/config.lua"
mods = {
repair = { enabled = true, price = 500, percentVehVal = 0.01 },
performance = { enabled = true, price = 500, percentVehVal = 0.01, priceMult = 0.1 },
cosmetics = { enabled = true, price = 500, percentVehVal = 0.01, priceMult = 0.1 },
stance = { enabled = true, price = 500, percentVehVal = 0.01 },
respray = { enabled = true, price = 500, percentVehVal = 0.01 },
wheels = { enabled = true, price = 500, percentVehVal = 0.01, priceMult = 0.1 },
neonLights = { enabled = true, price = 500, percentVehVal = 0.01 },
headlights = { enabled = true, price = 500, percentVehVal = 0.01 },
tyreSmoke = { enabled = true, price = 500, percentVehVal = 0.01 },
bulletproofTyres = { enabled = true, price = 500, percentVehVal = 0.01 },
extras = { enabled = true, price = 500, percentVehVal = 0.01 }
},
```
There are three pricing modes. `Config.ModsPricesAsPercentageOfVehicleValue` and `Config.ModsPricesIncludeFixedPrice` select which one is used across every mechanic location.
### Percentage pricing [#percentage-pricing]
Enable percentage pricing in `config/config.lua`:
```lua title="config/config.lua"
Config.ModsPricesAsPercentageOfVehicleValue = true
Config.ModsPricesIncludeFixedPrice = false
```
The base price for each category is the vehicle value multiplied by `percentVehVal`. The value is a decimal, so `0.01` means 1%.
For example, this makes performance upgrades start at 1% of the vehicle's value:
```lua title="config/config.lua"
performance = { enabled = true, price = 500, percentVehVal = 0.01, priceMult = 0.1 },
```
For a vehicle worth `100000`, the base price is `1000`. The `price` value is ignored in this mode.
JG Mechanic checks these sources for the vehicle value, in this order:
1. The price in the JG Dealerships database, if `jg-dealerships` is running and the vehicle is listed.
2. The vehicle price in QBCore or Qbox shared vehicle data.
3. The GTA model value.
If none of these returns a value, the script uses `50000`.
Addon vehicles with missing or inaccurate prices will produce unexpected upgrade prices. Add correct vehicle values or use fixed pricing instead.
### Combined pricing [#combined-pricing]
Enable both pricing settings to add each mod's fixed `price` to its percentage price:
```lua title="config/config.lua"
Config.ModsPricesAsPercentageOfVehicleValue = true
Config.ModsPricesIncludeFixedPrice = true
```
For example:
```lua title="config/config.lua"
performance = { enabled = true, price = 500, percentVehVal = 0.01, priceMult = 0.1 },
```
For a vehicle worth `100000`, the percentage price is `1000`. The fixed `price` of `500` is then added, making the base performance price `1500`.
### Fixed pricing [#fixed-pricing]
Disable percentage pricing to use the `price` from each `mods` entry:
```lua title="config/config.lua"
Config.ModsPricesAsPercentageOfVehicleValue = false
```
For example:
```lua title="config/config.lua"
performance = { enabled = true, price = 1000, percentVehVal = 0.01, priceMult = 0.1 },
```
Every vehicle will now use a base performance price of `1000`. `percentVehVal` is ignored.
`Config.ModsPricesIncludeFixedPrice` has no effect while percentage pricing is disabled.
### Higher upgrade levels [#higher-upgrade-levels]
`priceMult` increases the price of higher performance, cosmetic and wheel options. It applies in fixed, percentage and combined pricing modes.
Standard mods don't support a separate price for each upgrade option. Set one base price for the category, then use `priceMult` to increase the price for higher mod indexes.
A `priceMult` of `0.1` adds 10% of the base price for each GTA mod index above `0`. With a base price of `1000`, an option at mod index `3` costs `1300`.
Set `priceMult = 0` if every option in that category should cost the same. The default plate styles, window tints and horns already ignore this multiplier.
## Custom tuning parts [#custom-tuning-parts]
Custom tuning includes engine swaps, drivetrains, turbocharging, tyres, brakes, drift tuning and gearboxes.
Each mechanic location decides whether a category uses items or money:
```lua title="config/config.lua"
tuning = {
engineSwaps = { enabled = true, requiresItem = false },
}
```
* With `requiresItem = true`, applying an upgrade consumes its `itemName`. Its `price` is ignored.
* With `requiresItem = false`, applying an upgrade takes its `price` from the mechanic's society fund.
Set each option's cost in `config/config.tuning.lua`:
```lua title="config/config.tuning.lua"
[1] = {
name = "I4 Turbo 2.5L",
itemName = "i4_engine",
price = 30000,
}
```
`Config.ModsPricesAsPercentageOfVehicleValue` and `Config.ModsPricesIncludeFixedPrice` don't affect custom tuning.
These option prices are shared across every mechanic location. The location's `requiresItem` setting still decides whether that mechanic uses the item or the price.
The custom tuning `price` is a cost to the mechanic business. It isn't an automatic customer charge. Create an invoice in the tablet if the customer also needs to pay for the work.
## Shop and servicing item prices [#shop-and-servicing-item-prices]
Set the purchase price for an item inside the mechanic location's `shops` table:
```lua title="config/config.lua"
items = {
{ name = "engine_oil", label = "Engine Oil", price = 50 },
{ name = "tyre_replacement", label = "Tyre Replacement", price = 2500 },
}
```
The price is charged once per item. Buying four tyre replacements at `2500` costs `10000`.
When `Config.UseSocietyFund = true`, shop purchases use the mechanic's society fund. When it is `false`, they use the employee account selected by `Config.PlayerBalance`.
Servicing doesn't have a separate labour price. It consumes the items and quantities set in `config/config.servicing.lua`. Charge the customer through an invoice if needed.
## Invoices and commission [#invoices-and-commission]
Invoice line items and prices are entered by the mechanic in the tablet. There isn't a config setting for preset invoice prices.
For owned mechanics, `commission` pays a percentage of a completed order or paid invoice to the employee:
```lua title="config/config.lua"
commission = 10,
```
With `commission = 10`, the employee receives 10% and the remaining 90% stays in the society fund. Commission comes out of the amount paid. It isn't added on top of the customer's price.
## Currency display [#currency-display]
`Config.Currency` and `Config.NumberAndDateFormat` control how prices are displayed:
```lua title="config/config.lua"
Config.NumberAndDateFormat = "en-US"
Config.Currency = "USD"
```
Changing these settings doesn't convert any prices. Update the configured amounts yourself if you change your server's currency or economy.
Restart `jg-mechanic` after changing pricing settings.
# Props (/mechanic/props)
JG Mechanic uses a few props to create a more realistic experience when modding a vehicle. Some of you may use an anti-cheat system which requires allowing props to be spawned.
Below is a list of all the props used within JG Mechanic.
| Name | Hash |
prop_car_engine_01 | 232216084 |
xs_prop_x18_engine_hoist_02a | -1928024499 |
prop_wheel_01 | -292162984 |
prop_gascyl_01a | 1270590574 |
prop_tool_spanner01 | -2050576199 |
prop_paint_spray01b | -1788911489 |
prop_cs_tablet | -1585232418 |
# Servicing (/mechanic/servicing)
JG Mechanic tracks the health of each serviceable part as a vehicle gains mileage. Worn parts gradually affect the vehicle's handling. A mechanic can inspect and replace them through the Servicing app on the tablet.
Servicing supports cars, motorbikes and quad bikes. Part health is saved with the vehicle, so it continues from the same point after a restart.
## Enable or disable servicing [#enable-or-disable-servicing]
The main servicing settings are in `config/config.lua`.
Set `Config.EnableVehicleServicing` to `false` to stop servicing wear for every vehicle:
```lua title="config/config.lua"
Config.EnableVehicleServicing = false
```
To keep servicing enabled but exclude certain vehicles, add their spawn codes to `Config.ServicingBlacklist`:
```lua title="config/config.lua"
Config.ServicingBlacklist = {
"police",
"police2"
}
```
## Set the service interval [#set-the-service-interval]
Two settings decide when a vehicle needs servicing:
* `lifespanInKm` in `config/config.servicing.lua` controls how fast each part wears. A part with `lifespanInKm = 500` drops from `100%` to `0%` over 500 km. Increase this value for a longer interval, or decrease it for a shorter interval.
* `Config.ServiceRequiredThreshold` in `config/config.lua` controls when the vehicle is flagged as needing a service. A higher percentage flags it sooner. A lower percentage lets the part wear further before it is due.
For example, a part with a lifespan of 500 km and a service threshold of `20%` will be due after roughly 400 km:
```lua title="config/config.servicing.lua"
lifespanInKm = 500
```
```lua title="config/config.lua"
Config.ServiceRequiredThreshold = 20
```
The threshold only decides when the warning appears. It doesn't change how fast the part wears or how wear affects the vehicle.
Changing an interval doesn't reset a vehicle's current part health. The new wear rate is used as the vehicle gains more mileage. Replacing a part through the tablet returns that part to `100%`.
## Configure each part [#configure-each-part]
Edit the existing parts in `Config.Servicing` in `config/config.servicing.lua`:
| Option | What it does |
| -------------- | --------------------------------------------------------------- |
| `lifespanInKm` | Distance in kilometres for the part to wear from `100%` to `0%` |
| `itemName` | Inventory item used when a mechanic replaces the part |
| `itemQuantity` | Number of items used when the part is replaced |
| `restricted` | Limits the part to `"combustion"` or `"electric"` vehicles |
Make sure every `itemName` exists in your inventory. See [Inventory Items](/mechanic/inventory-items) for the default servicing items and [Pricing](/mechanic/pricing) to set their shop costs.
The default parts affect vehicles as follows:
* Suspension affects suspension stiffness, anti-roll force and body damage sounds.
* Tyres affect traction.
* Brake pads affect braking force.
* The clutch affects gear changes.
* Spark plugs and EV batteries affect acceleration.
* Engine oil, air filters, EV coolant and EV motors affect acceleration and top speed.
These effects become stronger as each part wears. An overdue vehicle may accelerate more slowly, lose top speed or grip, brake less effectively, or take longer to change gear. Replacing the worn part through the tablet restores that part's performance.
Don't add, remove or rename the servicing part entries. The tablet and vehicle handling use the existing part names.
The `enableDamage` option in `config/config.servicing.lua` doesn't currently stop an individual part from wearing. Leave it set to `true`. Use `Config.EnableVehicleServicing` or `Config.ServicingBlacklist` when you need to prevent servicing wear.
Servicing changes the vehicle's existing handling values. Addon vehicles with unusual handling can react differently as parts wear, so test your intervals with the vehicles used on your server.
Restart `jg-mechanic` after changing either config file.
## Troubleshooting [#troubleshooting]
Worn servicing parts reduce vehicle performance by design. If the Servicing app shows that the parts are healthy, another resource may be changing the same handling values after `jg-mechanic` applies them. Whichever resource applies its changes last will control those values.
1. Fully service the vehicle and test it again.
2. If the problem remains, fully restart your server with the other handling resource disabled.
3. Spawn the same vehicle and repeat the test under the same conditions.
4. Re-enable the other resource, fully restart and repeat the test once more.
If the problem only happens while both resources are enabled, configure one resource so it doesn't change the overlapping handling values. If that isn't possible, use only one of the resources for those changes.
# Troubleshooting (/mechanic/troubleshooting)
## Failed to load audiobank [#failed-to-load-audiobank]
You may see this error while `jg-mechanic` is starting:
```txt
failed to load audiobank 'audiodirectory/jg_mechanic'
```
The JG Mechanic sound bank didn't finish loading before FiveM timed out. Disable the script's sounds so it can start without the sound bank.
1. Open `config/config.lua`.
2. Set `Config.DisableSound` to `true`:
```lua title="config/config.lua"
Config.DisableSound = true
```
3. Fully restart your server.
## SQL error while starting JG Mechanic [#sql-error-while-starting-jg-mechanic]
You may see an error containing:
```txt
[SQL ERROR]
```
JG Mechanic tries to make the required database changes automatically. In rare cases this fails, and you need to make the changes manually.
1. Open `jg-mechanic/install/database`.
2. Run `run.sql` in your database software, such as phpMyAdmin or HeidiSQL.
If the error says `cannot use the syntax IF NOT EXISTS`, remove every instance of `IF NOT EXISTS` from `run.sql`, then run the file again.
Make sure you're running `run.sql` in your server's database. Check the database name before running it.
# Tuning (/mechanic/tuning)
JG Mechanic includes custom tuning functionality, such as engine swaps, custom drivetrains, traction options and more. These options aren't available as part of the base GTA game.
These options directly modify the vehicle's [*handling file*](https://gtamods.com/wiki/Handling.meta), which can be complex. We provide a set of default custom tuning options that will appeal to most players, but you may want to create your own. If you don't understand how handling files work, you'll likely run into problems.
All customisation is done in `config/config.tuning.lua`. This page is an FAQ for that file.
## Setting upgrade prices [#setting-upgrade-prices]
Standard GTA mods and custom tuning parts use different price settings. See [Pricing](/mechanic/pricing) for both methods, percentage pricing, shop costs and employee commission.
## I did an engine swap, why is my vehicle slower? [#i-did-an-engine-swap-why-is-my-vehicle-slower]
The script isn't broken. This functionality modifies the vehicle's handling file, and some vehicles can have wildly different handling files. The default configured handling changes therefore don't work well on all vehicles. They've been specifically tuned for the vanilla GTA vehicles.
This typically happens with addon vehicles, when their handling file is unbalanced. By default, our engine swaps **overwrite** certain handling values (see below). If some of the values modified as part of the swap were set too high in the vehicle's base handling file to compensate for other values that were set improperly, this can actually make your vehicle slower.
In these cases, it's unfortunately up to you to modify `config.tuning.lua` to make the handling changes more appropriate for the vehicles in your server. Though what we'd recommend more is improving the handling files of the addon vehicles in your server.
## Performance upgrades and engine swaps [#performance-upgrades-and-engine-swaps]
These are separate upgrade workflows:
* Performance upgrades in a mechanic bay are standard GTA mods, such as engine levels, brakes, transmission, suspension, armour and turbo. When a mechanic location requires items, every upgrade in this category uses `Config.Mods.ItemsRequired.performance.itemName` from `config/config.mods.lua`. The default item is `performance_part`. Higher upgrade levels don't use different items.
* Engine swaps in the tablet's Tuning app are custom tuning options from `Config.Tuning.engineSwaps` in `config/config.tuning.lua`. When a mechanic location has `requiresItem = true` for engine swaps, each option uses its own `itemName`, such as `i4_engine` or `v8_engine`.
The `itemName` values below only apply to custom tuning options. They don't change the item used for standard performance upgrades in the bay menu.
## What do the different config options mean? [#what-do-the-different-config-options-mean]
| Parameter | Explanation |
| name | The name of the modification that will be shown in the tablet. |
| info (optional) | Additional info that shows in the UI when the mechanic clicks the info icon while selecting an upgrade. Use it to warn mechanics about vehicles an upgrade shouldn't be applied to, or to share results from your testing of handling values. |
| itemName | For mechanics set up to use an item for upgrades, this is the name of the required item. |
| price | For mechanics set up to purchase upgrades, this is the cost to the mechanic to apply the upgrade. |
| audioNameHash | Any in-game vehicle name, or addon sound pack name (engine swaps only). |
| handling | Add or remove handling attributes and values. More help and info on handling values: gtamods.com/wiki/Handling.meta |
| handlingApplyOrder | The order in which this tuning option should be applied. This is useful when tuning options have overlapping handling values. Provide a priority number, and the lowest numbers will be applied first. |
| handlingOverwritesValues | Whether the handling values provided should overwrite the vehicle's existing values, or modify them. This works for negative values too. For example: a vehicle's current fDriveInertia value is at 1.0. - Set to "true" and a value of 0.5 sets
fDriveInertia to 0.5. - Set to "false", and a value of 0.5 will mean [1.0 + 0.5] and set
fDriveInertia to 1.5.
|
| restricted | Can be false (unrestricted), "electric" or "combustion". |
| blacklist | List of archetype names (spawn codes) that cannot use this modification. |
# Config & Customisation (/pro-scoreboard/config-and-customisation)
You can configure the script inside `config.lua`. Most of the config options are self-explanatory, but this guide covers them anyway.
## Highlighted Jobs [#highlighted-jobs]
* The colour of each highlighted job must be a hex code
* `countOnDutyOnly` displays the total players in that whitelisted job who are currently set as on duty (QBCore only)
* Icons are from [https://icons.getbootstrap.com/](https://icons.getbootstrap.com/) - paste the name of the icon (without the `bi-` prefix) to change it
* `ShowAdminBadges` shows or hides orange stars next to players who have admin or god status
* `AdminBadgeIcon` uses the same bootstrap icons as above
* You can set multiple jobs in one highlighted job counter. For example, if you have multiple mechanic jobs: `job = {"mechanic", "mechanic2"}`
## Name & Logo [#name--logo]
* Change the name by editing `Config.ServerName`.
* Change the icon displayed in the top left by replacing the PNG at `/html/my-logo.png`.
The replacement image **MUST** be a `.png` with a square aspect ratio.
## Key Bind [#key-bind]
* `Config.KeyBind` is the default key bind for all players. Individual players can change it by going to `Settings > Key Bindings > FiveM > (jg-scoreboard) Open Scoreboard` in-game.
# Installation (/pro-scoreboard/installation)
Do **NOT** change the name of the resource. Changing the folder name or any other names will break the script.
### Install the resource [#install-the-resource-step]
Extract the zip and move the `jg-scoreboard` resource into the `resources/[qb]` or `resources/[esx_addons]` folder in your server.
### Configure the resource [#configure-the-resource-step]
Configure the `config.lua` file as needed.
### Disable conflicting keybinds [#disable-conflicting-keybinds-step]
If you still have `qb-scoreboard` installed, disable it, as it uses the same `HOME` keybind. Or change the keybind in the config.
# API (/vehicle-studio/api)
JG Vehicle Studio has two exports for getting vehicle image URLs from other scripts. Both work on the client and the server.
## getImage [#getimage]
Returns the stored image URL for a vehicle and an ordered list of computed fallback image URLs.
```lua
-- works on both client & server
-- imageId is optional and defaults to "default" (the image set ID)
local image, fallbacks = exports["jg-vehiclestudio"]:getImage(spawnCode, imageId)
-- example
local image, fallbacks = exports["jg-vehiclestudio"]:getImage("adder")
-- example: use a specific image set
local orangeImage = exports["jg-vehiclestudio"]:getImage("adder", "orange_bg")
```
| Returns | Type | Description |
| ----------- | ---------- | ------------------------------------------------------------------------------------ |
| `image` | `string?` | Stored image URL for the vehicle & image set, or `nil` when none is saved |
| `fallbacks` | `string[]` | Ordered fallback URLs with `{MODEL}`, `{model}`, `{HASH}` & `{hash}` tokens resolved |
Fallback URLs are candidates only - the export does not check that they exist. Try them in order and use the first one that loads. When a fallback URL has no variable tokens it is treated as a final static fallback, and any configured fallbacks after it are not returned.
## getImages [#getimages]
Returns stored image URLs and computed fallback URLs for multiple vehicles in one call.
```lua
-- works on both client & server
-- imageId is optional and defaults to "default"
local images = exports["jg-vehiclestudio"]:getImages(spawnCodes, imageId)
-- example
local images = exports["jg-vehiclestudio"]:getImages({ "adder", "zentorno", "t20" })
--[[ example result:
{
adder = {
image = "https://images.example.com/jg-vehiclestudio/image/adder.webp?v=1710000000",
fallbacks = {
"https://docs.fivem.net/vehicles/adder.webp",
"https://cfx-nui-jg-vehiclestudio/web/dist/no-vehicle-image.png",
},
},
zentorno = {
image = nil,
fallbacks = {
"https://docs.fivem.net/vehicles/zentorno.webp",
"https://cfx-nui-jg-vehiclestudio/web/dist/no-vehicle-image.png",
},
},
}
]]
```
| Returns | Type | Description |
| ----------------- | ------------------------- | ------------------------------------------- |
| `images` | `table` | One entry per requested vehicle |
| `entry.image` | `string?` | Stored image URL for that vehicle, or `nil` |
| `entry.fallbacks` | `string[]` | Ordered computed fallback image URLs |
## Image IDs [#image-ids]
`imageId` is the image set ID. If you don't pass one, Vehicle Studio uses `"default"`. Use the same image ID you selected when photographing the vehicle in Vehicle Studio.
```lua
local defaultImage = exports["jg-vehiclestudio"]:getImage("adder")
local orangeImage = exports["jg-vehiclestudio"]:getImage("adder", "orange_bg")
```
## Browser/NUI fallback handling [#browsernui-fallback-handling]
NUI integrations should try the primary `image` first, then each fallback URL in order. Use the first URL that successfully loads.
```ts
interface VehicleImageCandidates {
image?: string | null;
fallbacks?: string[];
}
const imageProbeCache = new Map();
function probeImage(url: string): Promise {
const cached = imageProbeCache.get(url);
if (cached !== undefined) return Promise.resolve(cached);
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
imageProbeCache.set(url, true);
resolve(true);
};
img.onerror = () => {
imageProbeCache.set(url, false);
resolve(false);
};
img.src = url;
});
}
export async function resolveVehicleImage(
candidates: VehicleImageCandidates,
): Promise {
const urls = [candidates.image, ...(candidates.fallbacks ?? [])].filter(
(url): url is string => typeof url === "string" && url.length > 0,
);
for (const url of urls) {
if (await probeImage(url)) return url;
}
return null;
}
```
# Data Storage (/vehicle-studio/data-storage)
Vehicle Studio can store its saved data in local resource files or in a MySQL database through `oxmysql`.
The data storage switch lives in `config/config.lua`:
```lua
Config.DataStorage = "local"
```
Valid values are:
| Value | Requires `oxmysql` | Best for |
| ------------ | ------------------ | --------------------------------------------- |
| `"local"` | No | Simple installs, testing, single-server usage |
| `"database"` | Yes | Shared data, easier backups, production usage |
Image uploads are configured separately with `Config.ImageStorageProvider`. Data storage decides where Vehicle Studio stores metadata such as vehicle image references, presets, and settings. It does not store preset thumbnails or generated image files in the database.
## What Gets Stored [#what-gets-stored]
Vehicle Studio stores:
* Vehicle gallery index data.
* Image references and public image URLs.
* Presets.
* Preset thumbnail references.
* Gallery settings.
Generated image files and preset thumbnail files use the configured image upload provider:
* `"local"` saves files inside the resource.
* `"qbox"`, `"fivemanage"`, `"r2"`, and `"s3"` save files remotely and store the returned URL as a reference.
## Local Storage [#local-storage]
Local storage is the default option and does not require any database setup.
In `config/config.lua`:
```lua
Config.DataStorage = "local"
```
With local data storage, Vehicle Studio writes JSON data to a folder called `local_data/` inside the resource.
Typical local data files include:
* `local_data/index.json`
* `local_data/presets/index.json`
* `local_data/presets/.json`
* `local_data/settings.json`
Use local storage if:
* You want the simplest setup.
* You do not want `oxmysql` as a dependency.
* You are testing the resource locally.
* You are only running one server instance.
### Local Storage Notes [#local-storage-notes]
Do not commit your live `config/` folder or generated data files. The resource includes `config_example/` for defaults, while `config/` is intended for your server-specific settings.
If you also use local image uploads, make sure you have followed the local image upload guide.
## Database Storage [#database-storage]
Database storage keeps Vehicle Studio data in MySQL through `oxmysql`.
In `config/config.lua`:
```lua
Config.DataStorage = "database"
```
You must also start `oxmysql` before Vehicle Studio in `server.cfg`:
```ini
ensure oxmysql
ensure jg-vehiclestudio
```
Vehicle Studio uses `oxmysql` exports, so `oxmysql` is only required when `Config.DataStorage` is set to `"database"`.
## Database Tables [#database-tables]
The database schema lives in:
```txt
sql/jg_vehiclestudio.sql
```
That SQL file is the source of truth for both automatic setup and manual setup.
It creates these tables:
| Table | Purpose |
| --------------------------- | ------------------------------------ |
| `jg_vehiclestudio_vehicles` | Vehicle entries in the gallery index |
| `jg_vehiclestudio_images` | Image references for each vehicle |
| `jg_vehiclestudio_presets` | Saved preset data and summary fields |
| `jg_vehiclestudio_settings` | Resource settings saved from the UI |
## Automatic Setup [#automatic-setup]
When database storage is enabled, Vehicle Studio tries to run `sql/jg_vehiclestudio.sql` automatically on startup.
For automatic setup to work:
1. `oxmysql` must be started.
2. The MySQL connection must be valid.
3. The configured database user must have permission to create tables.
4. `sql/jg_vehiclestudio.sql` must be included with the resource.
## Manual Setup [#manual-setup]
If automatic setup fails, manually run:
```txt
sql/jg_vehiclestudio.sql
```
Run it in your MySQL database using your preferred database tool, then restart Vehicle Studio.
## Troubleshooting [#troubleshooting]
Check that `oxmysql` is installed and started before Vehicle Studio:
```ini
ensure oxmysql
ensure jg-vehiclestudio
```
Check:
* The MySQL connection string used by `oxmysql`.
* The database user has `CREATE TABLE` permission.
* `sql/jg_vehiclestudio.sql` exists in the resource.
* The server console for SQL errors.
You can manually run `sql/jg_vehiclestudio.sql` if needed.
This is expected. Vehicle Studio stores image files through `Config.ImageStorageProvider`, then saves references to those files in local data or database data.
Use the image upload provider docs if generated images or preset thumbnails are not uploading correctly.
# Image Sets (/vehicle-studio/image-sets)
Image sets are named photo slots for a vehicle. They are useful if you want vehicles to have multiple images for different use cases. A transparent PNG for lists, and a cool stylised pic with a background for showcase images, for example.
They are controlled by the `imageId` value used when a photo is saved or requested through exports. The default image set is called `default`.
## Why image sets exist [#why-image-sets-exist]
A single vehicle can need more than one image. For example:
| Example image set ID | Example use |
default | Main gallery image. |
showroom | Dealership UI image. |
loading_banner | Loading screen image. |
transparent | Transparent-background image for custom UI layouts. |
Each vehicle stores images by image set. That means `adder/default` and `adder/showroom` are separate images.
## Image sets are shared names, not folders [#image-sets-are-shared-names-not-folders]
An image set is just an ID. It does not create a separate folder or global collection.
If you photograph `adder`, `banshee`, and `zentorno` with the image set `showroom`, each vehicle gets its own `showroom` image. Other scripts can then ask for the `showroom` image by passing the same image ID to the exports.
```lua
local showroomImage = exports["jg-vehiclestudio"]:getImage("adder", "showroom")
```
## Naming rules [#naming-rules]
In the UI, custom image set IDs:
| Rule | Detail |
| Allowed characters | Letters, numbers, and underscores. |
| Length | Up to 25 characters. |
| Default set | Use the Use default image set switch to save as default. |
Use lowercase underscore names for consistency, such as `showroom`, `website_banner`, or `transparent_bg`.
Internally, image IDs are sanitised before storage. Existing server-side paths also support hyphens, but the UI is designed around underscore IDs.
## Saving and replacing images [#saving-and-replacing-images]
When a vehicle is photographed with a new image set, Vehicle Studio adds that image set to the vehicle.
When a vehicle is photographed with an image set it already has, the existing image for that vehicle and image set is replaced.
For example:
| Action | Result |
Save adder with default | Creates or replaces the default image for adder. |
Save adder with showroom | Creates or replaces the showroom image for adder. |
Save banshee with showroom | Creates or replaces the showroom image for banshee. It does not affect adder. |
Retaking an image or bulk photographing with **All vehicles** can replace existing images in the selected image set.
## Bulk photography and image sets [#bulk-photography-and-image-sets]
Bulk Photograph uses the selected image set to decide what to process.
| Scope | Behaviour |
| ------------ | --------------------------------------------------------------------------------- |
| Only missing | Photographs vehicles that do not already have the selected image set. |
| All vehicles | Photographs every vehicle and replaces existing images in the selected image set. |
This is useful when you add a new image set later. For example, if every vehicle already has `default`, you can create a new `website_banner` set by bulk photographing **Only missing** for `website_banner`.
## File names and storage [#file-names-and-storage]
Local image files are stored in `exported_images/`.
| Image set | Local filename format |
| ---------------- | -------------------------------- |
| `default` | `.` |
| Custom image set | `_.` |
Examples:
```txt
exported_images/adder.webp
exported_images/adder_showroom.webp
exported_images/adder_website_banner.png
```
When database storage is enabled, metadata is stored by both `spawn_code` and `image_id`.
## Image fallbacks [#image-fallbacks]
If an image does not exist, Vehicle Studio can return a configured fallback image instead. Fallbacks are controlled in the Settings tab.
By default, Vehicle Studio tries the FiveM vehicle image URL and then the local no-image placeholder. If you need exports to return `nil` for missing images, disable image fallbacks in Settings.
## Using image sets from other scripts [#using-image-sets-from-other-scripts]
Pass the image set ID as the second argument to `getImage` or `getImages`.
```lua
local defaultImage = exports["jg-vehiclestudio"]:getImage("adder")
local showroomImage = exports["jg-vehiclestudio"]:getImage("adder", "showroom")
local images = exports["jg-vehiclestudio"]:getImages({
"adder",
"banshee",
"zentorno",
}, "website_banner")
```
See [Exports API](/vehicle-studio/api) for full API details.
# Installation (/vehicle-studio/installation)
### Installation [#installation-step]
1. Unzip the `jg-vehiclestudio-bundle`.
2. Drag the script folder (`jg-vehiclestudio`) into a new folder called `[jg]` within your server's `resources` folder.
3. Make sure you have the latest version of [ox\_lib](https://github.com/overextended/ox_lib/releases/latest) installed.
4. Optional: If you plan to use a database for data storage, see [Data Storage](/vehicle-studio/data-storage) and make sure [oxmysql](https://github.com/overextended/oxmysql/releases/latest) is installed on your server.
5. Inside your `server.cfg`, add a new line after your framework, dependencies, and other required resources have started:
```txt
ensure [jg]
```
### Configuration [#configuration-step]
Vehicle Studio's configuration files live in the `config` folder.
The main file is:
```txt
config/config.lua
```
Private upload credentials live in:
```txt
config/config.upload.lua
```
Do not put real upload API keys in public GitHub repositories, support tickets, or screenshots.
### Pick Image Upload Storage [#pick-image-upload-storage-step]
Before using Vehicle Studio on a live server, choose where generated images should be stored.
For live servers, we strongly recommend a remote upload provider:
```lua
Config.ImageStorageProvider = "qbox"
Config.ImageStorageProvider = "fivemanage"
Config.ImageStorageProvider = "r2"
Config.ImageStorageProvider = "s3"
```
Local storage is mainly for localhost testing:
```lua
Config.ImageStorageProvider = "local"
```
**Local storage does not work out of the box on most live servers because the in-game browser blocks uploads to plain HTTP public IPs.** Only use local storage on a live server if you have set up your own HTTPS reverse proxy.
Read the image upload guide before taking lots of photos:
* [Image Uploads Docs](/vehicle-studio/image-uploads)
* [Local Storage Guide](/vehicle-studio/image-uploads/local-storage)
### Data Storage [#data-storage-step]
Vehicle Studio can store its data in local files or in a database.
If you want to change where data is stored, follow the [Data Storage Docs](/vehicle-studio/data-storage).
### Quick Start [#quick-start-step]
Follow the quick start guide to get up and running with JG Vehicle Studio:
# Languages (/vehicle-studio/languages)
{/* Generated by scripts/update-languages.mjs from the jgscripts/translations
repository - do not edit by hand, changes will be overwritten. */}
JG Vehicle Studio is available in 4 languages, translated by the community.
## Setting the language [#setting-the-language]
In `config.lua`, set `Config.Locale` to the code of the language you want, then restart the resource:
```lua
Config.Locale = "en"
```
## Available languages [#available-languages]
| Language | `Config.Locale` value |
| -------- | --------------------- |
| English | `en` |
| Danish | `da` |
| French | `fr` |
| German | `de` |
## Contributing a translation [#contributing-a-translation]
Spotted a mistake, or want the script in your language? Translations live in our public [translations repository](https://github.com/jgscripts/translations), and anyone can contribute. See the [Translations](/getting-started/translations) guide for how to submit yours.
# Presets (/vehicle-studio/presets)
Presets are reusable photography setups. They let you capture one good setup, save it, and apply it to future single-vehicle or bulk photography runs.
## What a preset stores [#what-a-preset-stores]
A preset can store:
| Data | Examples |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| Camera | Angle, pitch, distance, field of view, and camera offsets. |
| Studio setup | Time, weather, screen colour, and whether the vehicle was positioned in the studio or on the map. |
| Vehicle position | Saved map/studio coordinates when available. |
| Vehicle appearance | Mod overrides, colour overrides, and plate overrides. |
| Image settings | Output size, format, quality, background, filters, colour adjustments, padding, positioning, and green screen settings. |
Visual controls that are only part of the current single-vehicle session, such as doors, windows, lights, and indicators, are not saved into presets.
## How presets are created [#how-presets-are-created]
Presets are created from the image editor save flow.
1. Take or retake a vehicle photo.
2. Position the vehicle and capture it.
3. Adjust the image in the editor.
4. Click **Save**.
5. When asked **Save as Preset?**, choose **Yes**.
6. Enter a preset name.
7. Click **Save Preset & Continue**.
The preset name is shown to users. Vehicle Studio also creates an internal preset ID from the name so it can save and load the preset reliably.
## How presets are applied [#how-presets-are-applied]
When you start photography, choose **Preset** in the setup modal and select a saved preset.
If **Review setup before photographing** is off, Vehicle Studio applies the preset and starts processing immediately. This is the fastest path for bulk photography.
If **Review setup before photographing** is on, Vehicle Studio opens the positioning screen and then the image editor before processing. Use this when you want to make a one-off adjustment before saving the image.
## Presets and bulk photography [#presets-and-bulk-photography]
Presets are especially useful for bulk photography because every vehicle can be processed with the same:
| Preset part | Why it matters |
| --------------------- | --------------------------------------------------------------------------- |
| Camera setup | Keeps every vehicle framed consistently. |
| Output size | Makes all images the same dimensions or dynamic sizing behaviour. |
| Background and edits | Keeps dealership/shop images visually consistent. |
| Vehicle modifications | Applies the same stored visual configuration when supported by the vehicle. |
For large batches, create and test the preset on one vehicle first. Then run Bulk Photograph using **Only missing** for the selected image set.
## Where presets are stored [#where-presets-are-stored]
Storage depends on `Config.DataStorage`.
| Storage mode | Where preset data is stored |
| ------------ | --------------------------------------------------------------------------------------------------------------------------- |
| `local` | `local_data/presets/index.json`, `local_data/presets/.json`, and thumbnails in `local_data/presets/thumbnails/`. |
| `database` | `jg_vehiclestudio_presets` and related settings in the configured database. |
Do not delete `local_data/` unless you want to remove locally stored Vehicle Studio data.
## Managing presets [#managing-presets]
Open the Presets tab to rename or delete presets.
Renaming changes the display name. The internal preset ID stays the same.
Deleting a preset removes the saved preset data and its thumbnail. It does not delete vehicle images that were already created with that preset.
## Tips [#tips]
Use clear preset names such as `Dealership Front`, `Transparent 1200`, or `Website Banner`.
Create separate presets for different output formats or backgrounds. For example, use one preset for transparent website images and another for in-game gallery thumbnails.
When using a preset for bulk work, keep **Review setup before photographing** off after you have tested the preset.
# Quick Start (/vehicle-studio/quick-start)
This guide covers the basic workflow for using JG Vehicle Studio after the resource has been installed and configured.
## Before you start [#before-you-start]
Make sure the resource is started after `ox_lib`.
In `config/config.lua`, check:
| Setting | What it controls |
| ----------------------------- | -------------------------------------------------------------------------------------------- |
| `Config.VehicleStudioCommand` | The command used to open Vehicle Studio. Default: `vehiclestudio`. |
| `Config.ImageStorageProvider` | Where finished images are stored. Use `local`, `qbox`, `fivemanage`, `r2`, or `s3`. |
| `Config.HttpBaseUrl` | Required for local image storage when the automatic endpoint cannot be reached from the NUI. |
| `Config.DataStorage` | Stores vehicle, image, preset, and settings data in `local_data/` or the database. |
If `Config.DataStorage` is set to `database`, make sure `oxmysql` is available. The SQL schema in `sql/jg_vehiclestudio.sql` is used for database storage.
## Open Vehicle Studio [#open-vehicle-studio]
Run the configured command in-game:
```txt
/vehiclestudio
```
The main window has three sections:
| Section | Purpose |
| Gallery | Add vehicles, import vehicles, photograph vehicles, retake images, preview images, and delete saved images. |
| Presets | Rename or delete saved photography presets. |
| Settings | Check upload configuration and configure image fallbacks. |
## Add vehicles [#add-vehicles]
Open the Gallery tab, then use **Add Vehicles**.
You can add vehicles in two ways:
| Option | Use it when |
| ------------ | -------------------------------------------------------------------------------------------------- |
| Add manually | You already know the spawn codes you want to photograph. |
| Import | You want to import vehicles from the configured framework, JG Dealerships, or an external dataset. |
Manual spawn codes are validated before they are added. Invalid models are skipped, and vehicles already in the gallery are not duplicated.
## Photograph one vehicle [#photograph-one-vehicle]
1. Select a vehicle in the Gallery.
2. Click **Take Photo** in the side panel.
3. Choose a size or a preset.
4. Choose the image set.
5. Click **Next**.
6. Position and modify the vehicle.
7. Click the capture button.
8. Edit the final image.
9. Click **Save**.
When saving, Vehicle Studio asks whether you want to save the current setup as a preset. Saving a preset is optional, but it is useful when you want the same camera, vehicle setup, and image edits for future photos.
## Bulk photograph vehicles [#bulk-photograph-vehicles]
Use **Bulk Photograph** from the Gallery when you want to process many vehicles with the same setup.
Bulk photographing can process:
| Scope | What happens |
| ------------ | -------------------------------------------------------------------------------------------------- |
| Only missing | Only vehicles without the selected image set are photographed. |
| All vehicles | Every gallery vehicle is photographed, and existing images in the selected image set are replaced. |
For the fastest repeatable workflow, create a preset first, then use that preset in Bulk Photograph. If you do not enable **Review setup before photographing**, Vehicle Studio skips the manual positioning and editor steps and starts processing with the preset settings.
## Manage saved images [#manage-saved-images]
Selecting a vehicle opens its side panel. From there, you can:
| Action | What it does |
| -------------- | ------------------------------------------------ |
| Preview | Opens the saved image at a larger size. |
| Retake | Starts photography again for the same image set. |
| Delete image | Deletes one image set for that vehicle. |
| Delete vehicle | Deletes the vehicle and all saved images for it. |
Retaking an image with the same image set replaces the existing image.
Deleting saved images removes them from Vehicle Studio storage. If you use remote storage, make sure the provider credentials still allow deletion.
## Manage presets [#manage-presets]
Open the Presets tab to rename or delete saved presets. Presets are listed with their thumbnail and output size where available.
[Learn more about presets](/vehicle-studio/presets)
## Use images in other scripts [#use-images-in-other-scripts]
Other resources can use the `getImage` and `getImages` exports to display saved images in their own UI.
```lua
local imageUrl = exports["jg-vehiclestudio"]:getImage("adder")
```
See [Exports API](/vehicle-studio/api) for full examples.
# Custom Framework (/vehicle-mileage/custom-framework)
Using a custom framework with JG Vehicle Mileage is fairly straightforward. All your framework needs is some sort of owned vehicles database table with a `plate` column. Then:
1. Go to `jg-vehiclemileage/main.lua`
2. Add another conditional for your framework, and point to the name of your vehicles table
3. In `jg-vehiclemileage/config.lua`, set `Config.Framework = [your framework name]`
Example:
```lua
if Config.Framework == "QBCore" then
Framework.VehiclesTable = "player_vehicles"
elseif Config.Framework == "ESX" then
Framework.VehiclesTable = "owned_vehicles"
elseif Config.Framework == "MyFrameworkName" then
Framework.VehiclesTable = "vehicles_table_name"
else
error("You haven't set a valid framework. Valid options can be found in main.lua!")
end
```
# Installation (/vehicle-mileage/installation)
## Installation [#installation]
### Install dependencies [#install-dependencies-step]
Make sure you have the latest versions of [ox\_lib](https://github.com/overextended/ox_lib/releases/latest) & [oxmysql](https://github.com/overextended/oxmysql/releases/latest) installed on your server.
### Download the resource [#download-the-resource-step]
Download from GitHub by going to the [latest release](https://github.com/jgscripts/jg-vehiclemileage/releases/latest) page & clicking "Source code (zip)".
### Rename the folder [#rename-the-folder-step]
Unzip the downloaded file & rename the folder to `jg-vehiclemileage`.
### Move the resource [#move-the-resource-step]
Drag the script folder (`jg-vehiclemileage`) into a new folder called `[jg]` within your server's `resources` folder.
### Start the resource [#start-the-resource-step]
In your `server.cfg`, add a new line **after** all your other resources have started:
```txt
ensure [jg]
```
## If you received an error saying \[SQL ERROR] [#if-you-received-an-error-saying-sql-error]
JG Vehicle Mileage tries to automatically make the required database changes. In some cases, this automatic installation fails and you need to make the changes manually.
Head into the `install` folder within `jg-vehiclemileage`. Run the `run-qb.sql` (QBCore/Qbox) or `run-esx.sql` (ESX Legacy) file in your database software (such as PhpMyAdmin or HeidiSQL).
If you get an error along the lines of `cannot use the syntax IF NOT EXISTS`, remove every instance of `IF NOT EXISTS` from the `run-qb.sql`/`run-esx.sql` file, and re-run it. It will still run fine.
Make sure you are running this SQL code in the correct database - triple check and cross reference the name of the database!
# Changelog (/advanced-garages/changelog)
# v2.2.2 (/advanced-garages/changelog/v.2.2.2)
This release improves reliability of vehicle spawning on the server by using `CreateVehicleServerSetter` instead of `CreateVehicle`.
## ?? Upgrade Requirements [#-upgrade-requirements]
* Server build 7290+ (you should do this anyway for an important security update - [download here](https://runtime.fivem.net/artifacts/fivem/build_server_windows/master/))
* Game version 2944+ (go to `server.cfg` -> `sv_enforcegamebuild 2944`)
## Potential Issues [#potential-issues]
There may be an issue with some vehicles or addons as a vehicle type is required. For all base GTA vehicles, I used a mismatch table from [tabarra](https://gist.github.com/tabarra/32ef90524188093ab4218ee7b5121269). An easy way to fix addons are to make sure the class in the `vehicles.meta` of the vehicle is correct. This will only typically be an issue with emergency vehicles, trailers or utility vehicles.
## Changed Files [#changed-files]
* client/cl-main.lua
* server/sv-main.lua
# v2.0.1 (/advanced-garages/changelog/v2.0.1)
## Changelog [#changelog]
* Fix Quasar housing garages being unable to drive vehicles
* Fix unusually high usage in resmon
* Fix private garages not appearing on player load
* Add missing code for paying into ESX society fund
## Files Changed [#files-changed]
* `config-cl.lua`
* `client/cl-garage.lua`
* `framework/sv-functions.lua`
## Upgrading from v2.0 [#upgrading-from-v20]
Simply replace the files in "Files Changed" above
# v2.0.2 (/advanced-garages/changelog/v2.0.2)
## Changelog [#changelog]
* Fix bug where job garage plates set to random would be blank
* Fix bug where job garages keys would not be given
* Add `vehicleEntity` to `Framework.Client.VehicleGiveKeys()` for compatibility with more key scripts + mk\_vehiclekeys and qs-vehiclekeys added as default options
* Fix pay into society fund for ESX
## Files Changed [#files-changed]
* `client`
* `cl-gang-garage.lua`
* `cl-garage.lua`
* `cl-impound.lua`
* `cl-job-garage.lua`
* `cl-vehicle.lua`
* `framework`
* `cl-functions.lua`
* `server`
* `sv-main.lua`
## Upgrading from v2.0.1 [#upgrading-from-v201]
Replace the files mentioned above - no config changes.
# v2.0.3 (/advanced-garages/changelog/v2.0.3)
## Changelog [#changelog]
* Fix `/vplate` command
* Add Swedish language
* Add `hyon_gas_station` and `cdn-fuel` as default fuel options
* Some minor code cleanup and small fixes
## Files Changed [#files-changed]
* `framework/cl-functions.lua`
* `client/cl-main.lua`
* `client/cl-vehicle.lua`
* `config-cl.lua`
* New language at `locales/sv.lua`
## Upgrading [#upgrading]
Replace the files listed above, no config changes\
# v2.0.4 (/advanced-garages/changelog/v2.0.4)
## Changelog [#changelog]
* Fix error when script would attempt to refresh blips on job or gang change
* Fix incorrect vehicle model for `qs-vehiclekeys` integration
## Files Changed [#files-changed]
* `framework/cl-functions.lua`
* `config-cl.lua`
## Upgrading [#upgrading]
Only replace the 2 files above!
# v2.0.5 (/advanced-garages/changelog/v2.0.5)
## Changelog [#changelog]
* Bug Fix - Vehicles with custom plates overwritten in ESX on spawn
* Bug Fix - /vreturn not compatible with spaces
* Bug Fix - Vehicle labels not pulling from shared in some cases
* Bug Fix - Prevent /vplate from allowing special characters
* Bug Fix - Vehicle can be spawned twice if model is still loading
* Bug Fix - When storing vehicle check hash as well as plate so a stolen vehicle can't be stored that has the same plate!
* Bug Fix - Detect data type before json.encode or json.decode vehicle properties
* Bug Fix - Spawned vehicles come out dirty
* Optimisation - Better error message when QBCore.Shared.Vehicles is broken
## Files Changed [#files-changed]
```
/client/cl-gang-garage.lua
/client/cl-garage.lua
/client/cl-impound.lua
/client/cl-job-garage.lua
/config-cl.lua
/framework/cl-functions.lua
/fxmanifest.lua
/main.lua
/server/sv-vehicle.lua
/web
```
## Upgrading [#upgrading]
Replace the files listed above, there are no `config.lua` changes
# v2.0.6 (/advanced-garages/changelog/v2.0.6)
## Changelog [#changelog]
* NULL appearing instead of vehicle name
## Files Changed [#files-changed]
```
client/cl-gang-garage.lua
client/cl-garage.lua
client/cl-job-garage.lua
client/cl-main.lua
framework/cl-functions.lua
```
## Upgrading [#upgrading]
Replace the files listed above
# v2.0.7 (/advanced-garages/changelog/v2.0.7)
## Changelog [#changelog]
* Finance integration with JG Dealerships
## Files Changed [#files-changed]
```
client/cl-garage.lua
server/sv-vehicle.lua
web (whole folder)
```
## Upgrading [#upgrading]
Replace the files listed above. No config changes!
# v2.1.1 (/advanced-garages/changelog/v2.1.1)
If you are upgrading from v2.0.7 or below, **you still need to follow the upgrading guide for v2.1**, it's super important! [https://docs.jgscripts.com/advanced-garages/changelog/v2.1](https://docs.jgscripts.com/advanced-garages/changelog/v2.1)
## Changelog [#changelog]
* Fix infinite loading and game freeze bug when taking vehicle out of garage
* Fix vector3 error when taking boats/planes out of garages
* Fix extras/liveries menu not working when `Config.DoNotSpawnInsideVehicle` is enabled
* Fix not being able to transfer to house garages with third-party house scripts
* Improved backwards compatibility to prevent `blips` table client error
* Improve `da` and `hu` locale files
## Files Changed [#files-changed]
Replace all files in `/client`, `/server`, `/locales`, `/web`, as well `/config-cl.lua`. There are no changes to the config.
# v2.1.2 (/advanced-garages/changelog/v2.1.2)
## Changelog [#changelog]
* Fix JG Dealerships finance integration not updating inside garages
* Integration with Sonoran
## Files Changed [#files-changed]
* `web` folder
* `server/sv-main.lua`
No config changes!
# v2.1 (/advanced-garages/changelog/v2.1)
This update had some major known bugs. Please upgrade the v2.1.1 patch: [https://docs.jgscripts.com/advanced-garages/changelog/v2.1.1](https://docs.jgscripts.com/advanced-garages/changelog/v2.1.1)
## Changelog [#changelog]
* Vehicle damage inconsistencies fixed!
* All new private garages manager: create, edit, delete and add multiple players
* Vehicle nicknames
* New "personal" job/gang garage type for storing personal vehicles
* Configure blip per-garage
* Vehicle P2P transfer blacklist
* UI to set livery and extras when using vehicle spawner garages
* ti\_fuel integration
* A LOT of bug fixes
## SQL [#sql]
You need to run the following SQL in your database to migrate to v2.1
```sql
-- player_priv_garages
ALTER TABLE player_priv_garages ADD COLUMN IF NOT EXISTS owners LONGTEXT;
UPDATE player_priv_garages SET owners = json_array(json_object('id', citizenid, 'name', (SELECT name FROM players WHERE citizenid = citizenid LIMIT 1)));
ALTER TABLE `player_priv_garages` DROP `citizenid`;
-- player_vehicles
ALTER TABLE `player_vehicles` ADD COLUMN IF NOT EXISTS `nickname` VARCHAR(255) DEFAULT '';
```
```sql
-- player_priv_garages
ALTER TABLE player_priv_garages ADD COLUMN IF NOT EXISTS owners LONGTEXT;
UPDATE player_priv_garages SET owners = json_array(json_object('id', owner, 'name', (SELECT firstname FROM users WHERE identifier = owner LIMIT 1)));
ALTER TABLE `player_priv_garages` DROP `owner`;
-- owned_vehicles
ALTER TABLE `owned_vehicles` ADD COLUMN IF NOT EXISTS `nickname` VARCHAR(255) DEFAULT '';
```
## Files Changed & Configuration [#files-changed--configuration]
For the smoothest migration, re-configure your script from scratch in the [Configurator](https://configurator.jgscripts.com/advanced-garages).
All files have been changed and some new ones added. Please re-install the script, then import your existing config.lua to the [https://configurator.jgscripts.com/advanced-garages](https://configurator.jgscripts.com/advanced-garages)
Make sure you triple check your configuration in the Configurator and make any necessary changes. Some new options have been added and other options removed. Most notably, blips are now applied on a per-garage basis, so make sure these are filled in!
# v2.2.0 (/advanced-garages/changelog/v2.2.0)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Major Changes [#major-changes]
* Vehicle thumbnails (optional) [preview](https://docs.jgscripts.com/advanced-garages/vehicle-images)
* Discord webhooks for logs
* Configurable markers
* Hide player names in transfer list
* Hide JG Scripts watermark
* Several new key + fuel integrations out of the box (see [Configurator](https://configurator.jgscripts.com/advanced-garages))
## Fixes [#fixes]
* Improved vehicle spawning error handling + spawn protection
* Secured all exploitable events
* Fixed ability to select UI elements
* Fixed impound "drive" spamming exploit
* Supressed "no object by ID" F8 console spam
* Detects new qb-banking update with new exports
* Can detect instances where job/gang ranks are stored as strings or integers
## Changed Files [#changed-files]
* client/cl-gang-garage.lua
* client/cl-garage.lua
* client/cl-impound.lua
* client/cl-job-garage.lua
* client/cl-liveries-extras-menu.lua
* client/cl-main.lua
* client/cl-private-garage.lua
* client/cl-vehicle.lua
* config-cl.lua
* framework/cl-functions.lua
* framework/esx/cl-esx.lua
* framework/esx/sv-esx.lua
* framework/sv-functions.lua
* fxmanifest.lua
* server/sv-gang-garage.lua
* server/sv-garage.lua
* server/sv-impound.lua
* server/sv-job-garage.lua
* server/sv-main.lua
* server/sv-private-garage.lua
* server/sv-vehicle.lua
* server/sv-webhooks.lua
* web (entire folder)
# v2.2.1 (/advanced-garages/changelog/v2.2.1)
## Fixes [#fixes]
* Fix bug when taking out vehicle from a housing garage integration
## Changed Files [#changed-files]
* client/cl-garage.lua
# v2.2.3 (/advanced-garages/changelog/v2.2.3)
**Highly recommended for anyone who updated to v2.2.2! Just a one file change!**
## Fixes [#fixes]
* Improves upon type checking from v2.2.2 - you should no longer have issues with addons, or need to update meta files in cases of vehicles incorrectly spawning!
**Game build concerns, you're OK!**
* To give a sigh of relief for people using older game builds - I was wrong about it, my bad. Game builds such as 2699, are **completely fine!** - you only need server build 7290+. Your game build doesn't matter, as long as it's reasonably modern.
## Changed Files [#changed-files]
* client/cl-main.lua
# v2.2.4 (/advanced-garages/changelog/v2.2.4)
## Features [#features]
* Can force liveries & extras on job/gang spawner garage vehicles without menu - example:
```lua
{
vehicle = "police",
plate = "PD",
minJobGrade = 0,
livery = 1,
extras = {1, 2}
}
```
* Added update checker
## Fixes [#fixes]
* Fixed error when spawning bicycles & quad bikes
* Fixed when server would pre-maturely timeout when model is downloading on the client
## Changed Files [#changed-files]
* client/cl-gang-garage.lua
* client/cl-job-garage.lua
* client/cl-main.lua
* server/sv-version-check.lua
* web (entire folder)
# v2.2.5 (/advanced-garages/changelog/v2.2.5)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
?? **Highly recommended update!**
## Fixes [#fixes]
* All remaining issues with vehicle spawning have been fixed
* Option to revert back to client side spawning in emergencies (`Config.DisableServerVehicleSpawning`)
* Fix bug where trailers with no seats wouldn't spawn
* Fix bug where you would spawn inside the car in house garages
* Fix bug with job/gang garage where vehicle keys would be assigned to the wrong plate
* Job/gang vehicle liveries are also applied via mod #48 for compatibility with more vehicles
## UI [#ui]
* Transfer vehicle UI redesign
## Changed Files [#changed-files]
* client/cl-gang-garage.lua
* client/cl-garage.lua
* client/cl-impound.lua
* client/cl-job-garage.lua
* client/cl-liveries-extras-menu.lua
* client/cl-main.lua
* config-cl.lua
* config.lua
* server/sv-main.lua
* fxmanifest.lua
* web (entire folder)
# v2.2.6 (/advanced-garages/changelog/v2.2.6)
Fix job/gang garage "spawner" vehicles with `plate = false` not receiving keys
## Changed Files [#changed-files]
* client/cl-gang-garage.lua
* client/cl-job-garage.lua
* fxmanifest.lua
# v2.2.7 (/advanced-garages/changelog/v2.2.7)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Fixes [#fixes]
* Fix players longer get stuck on a black screen when taking vehicle out
* Remove black screen transition when taking vehicle out
* Prevent vehicles spawning under the map
* Removed routing buckets (causing vehicles to sink under the map in some third-party MLOs)
* Fix "can't concatenate makeName" error
## Changed Files [#changed-files]
* client/cl-gang-garage.lua
* client/cl-garage.lua
* client/cl-impound.lua
* client/cl-job-garage.lua
* client/cl-main.lua
* framework/cl-functions.lua
* framework/esx/cl-esx.lua
* framework/qb/cl-qb.lua
* main.lua
* server/sv-main.lua
* fxmanifest.lua
# v2.2.8 (/advanced-garages/changelog/v2.2.8)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Fixes & Improvements [#fixes--improvements]
* Spawning defaults to not using `CreateVehicleServerSetter`
* Teleporting when spawning removed unless required by distance and spawning method
* Pulling out a vehicle should take significantly less time than before
* /vplate updates the vehicle props/mods to prevent plate inconsistencies
* `ShowHouseGarage` export compatibility with sea & air garages
* Create private garage menu can now create and edit sea & air garages
* Preventing saving of vehicle and body damage of nil or \< 0
* JG Mechanic integration
## Config options [#config-options]
```diff
+ `Config.UseCreateVehicleServerSetter = false`
- `Config.DisableServerVehicleSpawning = true`
```
## Locales [#locales]
```diff
+ vehicleNeedsService = "Needs Service",
+ type = "Type",
```
## Changed Files [#changed-files]
* client/cl-gang-garage.lua
* client/cl-garage.lua
* client/cl-impound.lua
* client/cl-job-garage.lua
* client/cl-main.lua
* client/cl-private-garage.lua
* config.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* locales/cn.lua
* locales/cs.lua
* locales/da.lua
* locales/de.lua
* locales/en.lua
* locales/es.lua
* locales/fi.lua
* locales/hu.lua
* locales/it.lua
* locales/lt.lua
* locales/nl.lua
* locales/pt.lua
* locales/sv.lua
* locales/vi.lua
* server/sv-gang-garage.lua
* server/sv-garage.lua
* server/sv-job-garage.lua
* server/sv-main.lua
* server/sv-private-garage.lua
* server/sv-vehicle.lua
* fxmanifest.lua
* web (entire folder)
# v2.2.9 (/advanced-garages/changelog/v2.2.9)
## Hotfix [#hotfix]
* Vehicles coming out without mods/incorrect plate
**You just need to replace `client/cl-main.lua` and nothing else!**
## Changed Files [#changed-files]
* client/cl-main.lua
* fxmanifest.lua
# v3.0.1 (/advanced-garages/changelog/v3.0.1)
## Qbox only fix [#qbox-only-fix]
My apologies - had the incorrect framework files on my laptop without the Qbox changes!
## Changed Files [#changed-files]
* framework/cl-functions.lua
* fxmanifest.lua
# v3.0.2 (/advanced-garages/changelog/v3.0.2)
## Fixes [#fixes]
* Fix invalid model error when entering garage interiors
* Fix spawner vehicles not giving keys (and added `maxMods` to the config, sorry!)
* Vehicle keys are now removed (renewed, qs-vehiclekeys, etc)
## Config errors [#config-errors]
If you're upgrading to v3 from v2, please re-configure the script and *DON'T just copy over your existing config*! You don't need to modify your database.
## Changed Files [#changed-files]
* client/cl-garage.lua
* client/cl-interior.lua
* client/cl-locations.lua
* client/cl-main.lua
* client/cl-spawn.lua
* config/config.example.lua
* fxmanifest.lua
# v3.0.3 (/advanced-garages/changelog/v3.0.3)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Fixes [#fixes]
* Fix vehicle labels with duplicate make names & displaying hashes
* Fix teleport exploit when trying to enter an empty garage interior
* Fix vreturn command
* Fix issue where job/gang garages with multiple jobs couldn't store vehicles
* Unescrowed `cl-locations.lua` and set Z height to 4.0 by default
* Disable interior garages for sea & air garages (for now)
* Fix players going into negative balance when paying for impound & return fees
* Added missing ti\_fuel integration code
* Disable functionality for people who don't want to update JG Mech/Dealerships
* Improve AdvancedParking integration
## Changed Files [#changed-files]
* client/cl-garage.lua
* client/cl-impound.lua
* client/cl-interior.lua
* client/cl-locations.lua
* client/cl-main.lua
* framework/cl-functions.lua
* framework/main.lua
* framework/sv-functions.lua
* fxmanifest.lua
* server/sv-garage.lua
* server/sv-main.lua
* server/sv-spawn.lua
* server/sv-vehicle.lua
* shared/main.lua
* fxmanifest.lua
# v3.0.4 (/advanced-garages/changelog/v3.0.4)
## Fixes [#fixes]
* Accidently disabled "Vehicle is already out" check, so vehicles could be constantly respawned
## Improvements [#improvements]
* Garage `coords` now accept a vec4 for rotating the box zone
* Useful error message when impound is missing `job` in the config
## Changed Files [#changed-files]
* client/cl-impound.lua
* client/cl-locations.lua
* server/sv-garage.lua
* shared/main.lua
* fxmanifest.lua
# v3.0.5 (/advanced-garages/changelog/v3.0.5)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
**You should update to this Garage version ASAP if you've updated to Mechanic v1.0.11**
## Fixes [#fixes]
* \*Added new export for Mechanic v1.0.11 when using /vplate to ensure no data loss and smooth integration
* Fix impound spawning vehicle when you don't have enough money
* Fix impound not paying into society fund
* Added more verbose messages for "Could not transfer", "Something went wrong", "Could not take vehicle out", etc.
* Fix rare error if you have a column in your DB called `model`
* Uppercase vehicle plates to prevent keys errors
* Increase all model loading times to 60secs for your damn high poly TOS breaking vehicles
* Fix /vplate not updating the props with updated plate immediately
## Changed Files [#changed-files]
* client/cl-garage.lua
* client/cl-impound.lua
* client/cl-private-garages.lua
* client/cl-spawn.lua
* client/cl-vehicle.lua
* framework/cl-functions.lua
* framework/main.lua
* server/sv-garage.lua
* server/sv-impound.lua
* server/sv-private-garages.lua
* server/sv-spawn.lua
* server/sv-vehicle.lua
* shared/main.lua
* fxmanifest.lua
* web (entire folder)
# v3.0.6 (/advanced-garages/changelog/v3.0.6)
## Fixes [#fixes]
* Fix bug where some players could see all impounded vehicles (ESX only)
* Fix bug where some housing integrations would report a vector error
* Fix impound timezone mismatch error
* Increase timeout for DoesEntityExist from 1s->10s (client spawning only)
* Fix bug where vehicles re-spawned with AdvancedParking could be taken out from garage
* Add missing webhook calls (vehicle stored, player & garage transfers)
* Fix bug where fake plate would be stored in props JSON, breaking server spawning
* Fix bug where you could use an existing plate in /vplate
## Changed Files [#changed-files]
* client/cl-garage.lua
* client/cl-spawn.lua
* config/config.example.lua
* framework/main.lua
* server/sv-garage.lua
* server/sv-impound.lua
* server/sv-vehicle.lua
* fxmanifest.lua
* web (entire folder)
# v3.0.7 (/advanced-garages/changelog/v3.0.7)
**Fixes AdvancedParking integration bug: `Parameter "plate" must be a string`**
## Changed Files [#changed-files]
* server/sv-vehicle.lua
* fxmanifest.lua
# v3.0.8 (/advanced-garages/changelog/v3.0.8)
## Fixes [#fixes]
* Fix issue where `AllowInfiniteVehicleSpawns` config options would have no effect
* Fix issue where take out event was not firing when `showLiveriesExtrasMenu = true`
## Changed Files [#changed-files]
* client/cl-garage.lua
* server/sv-garage.lua
* server/sv-vehicle.lua
* fxmanifest.lua
# v3.1.0 (/advanced-garages/changelog/v3.1.0)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## September Update [#september-update]
## Security [#security]
* Fixed 1 major exploit
* Fixed 3 minor exploits (unreported, found internally)
* Prevent vehicle spawn if you are moved from the garage while menu is open
* Added `Config.DisableTransfersToUnregisteredGarages` for optional improved security when using unregistered garage events via integrations
## Features [#features]
* Configure camera transition for garage interiors
* Server-spawned vehicles are placed in player's current bucket
* Utilises `vehicleid` statebag for better integration across the Qbox ecosystem
* Auto-run SQL on first start
* New callback event (config-cl.lua) for approving player vehicle transfers
## Fixes [#fixes]
* "group.admin" is no longer hardcoded for some chat commands
* Fix "TakeOutVehicle" event not running when showLiveries menu is false
* Add check for Brazzers-FakePlate to server-side plate check events
* Fix `Config.InsertVehicleKeyBind` having no effect
* Fix bug where previous garage vehicle names would persist until garage is closed and re-opened
* `getAllGarages` export now returns all garages (incl impounds, job, private, gang)
* Fix issue where editing a private garage would result in "NAME\_TAKEN" error.
## Changed Files [#changed-files]
* client/cl-garage.lua
* client/cl-impound.lua
* client/cl-interior.lua
* client/cl-locations.lua
* client/cl-main.lua
* client/cl-spawn.lua
* client/cl-vehicle.lua
* config/config-cl.lua
* config/config-sv.lua
* config/config.example.lua
* framework/sv-functions.lua
* server/sv-garage.lua
* server/sv-impound.lua
* server/sv-initsql.lua
* server/sv-locations.lua
* server/sv-main.lua
* server/sv-private-garages.lua
* server/sv-society-garage.lua
* server/sv-spawn.lua
* server/sv-vehicle.lua
* fxmanifest.lua
* web (entire folder)
# v3.1.1 (/advanced-garages/changelog/v3.1.1)
## Fixes [#fixes]
* Fix `attempt to index a nil value` error when taking a vehicle out of housing integrations
* Fix issue where garage coords set to `vector4` would cause a comparison error
## Changed Files [#changed-files]
* client/cl-garage.lua
* server/sv-garage.lua
* server/sv-impound.lua
* fxmanifest.lua
# v3.1.2 (/advanced-garages/changelog/v3.1.2)
## Config callbacks [#config-callbacks]
* Also added callback to approve garage transfers to config-cl.lua
## Fixes [#fixes]
* Ragdoll exploit to duplicate vehicles
* Fix `plate = false` causing spawner vehicles to not spawn on ESX
* Force uppercase plates in vehicle keys functions
## Integrations [#integrations]
* JPR Housing
## Changed Files [#changed-files]
* client/cl-spawn.lua
* config/config-cl.lua
* framework/cl-functions.lua
* server/sv-garage.lua
* server/sv-spawn.lua
* fxmanifest.lua
# v3.1.3 (/advanced-garages/changelog/v3.1.3)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Fixes [#fixes]
* Fix issue where blips would double in size & all garage locations would become impounds
* Set spawner vehicles fixed before applying extras
* `Config.GangEnableCustomESXIntegration` to allow custom gang script integrations on ESX
## User Interface [#user-interface]
* Minor UX improvements to the private garages manager, edit & adding owner modals
## Changed Files [#changed-files]
* client/cl-locations.lua
* client/cl-spawn.lua
* client/cl-vehicle.lua
* config/config.example.lua
* framework/cl-functions.lua
* framework/esx/cl-esx.lua
* framework/qb/cl-qb.lua
* framework/qbx/cl-qbx.lua
* framework/sv-functions.lua
* server/sv-garage.lua
* server/sv-locations.lua
* server/sv-main.lua
* server/sv-private-garages.lua
* fxmanifest.lua
* web (entire folder)
# v3.1.4 (/advanced-garages/changelog/v3.1.4)
## Fixes [#fixes]
* Vehicle extras (job/gang garages) not re-applying after being disabled
* 'Spawner' vehicles with a custom plate will fail when using `SpawnVehiclesWithServerSetter`
* Impound dupe bug when using 'drive out' job functionality
* Impound vehicles driving out when clicking 'return to owner's garage'
## Changed Files [#changed-files]
* client/cl-impound.lua
* client/cl-spawn.lua
* client/cl-vehicle.lua
* framework/main.lua
* server/sv-garage.lua
* server/sv-impound.lua
* server/sv-spawn.lua
* fxmanifest.lua
# v3.1.5 (/advanced-garages/changelog/v3.1.5)
## Fixes [#fixes]
* Issue where vehicle could not be driven out of impound when client-sided spawning is enabled
* Remove event re-trigger delay for `get-available-garage-locations`
## Changed Files [#changed-files]
* client/cl-locations.lua
* server/sv-impound.lua
* fxmanifest.lua
# v3.1.6 (/advanced-garages/changelog/v3.1.6)
## Fixes [#fixes]
* Fix bug on ESX where spawner vehicles with `plate = false` would cause a native execution error
## Changed Files [#changed-files]
* server/sv-garage.lua
* fxmanifest.lua
# v3.2.0 (/advanced-garages/changelog/v3.2.0)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Features [#features]
* Add `fromGarageId` to transfer config function & webhook
* Add artifact issues check on start (uses artifacts.jgscripts.com)
* Added `origin` to vehicle give/remove keys ("personal", "job" or "gang")
* Add customisable insert & takeout verification callbacks, for adding custom checks
## Fixes [#fixes]
* Prevent ped being killed when storing an `air` vehicle
* Fix interiors vehicle deletion bug
## Integrations [#integrations]
* Stretcher is deleted upon storing a vehicle if using wasabi\_ambulance
* Support for t1ger\_keys job garage keys
* Fixed keys sometimes not being able to be removed when using qs-vehiclekeys
* Fuel: Renewed-Fuel, rcore\_fuel
* Keys: tgiann-hotwire
* Banking: Renewed-Banking (for Qbox), okokBanking, fd\_banking
* Third-party gangs support: rcore\_gangs
## Config [#config]
* Added `Config.Gangs` (by default `Config.Gangs = "auto"`)
## Changed Files [#changed-files]
* client/cl-garage.lua
* client/cl-impound.lua
* client/cl-interior.lua
* client/cl-spawn.lua
* client/cl-vehicle.lua
* config/config-cl.lua
* config/config.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* server/sv-garage.lua
* server/sv-impound.lua
* server/sv-locations.lua
* server/sv-society-garage.lua
* server/sv-spawn.lua
* server/sv-version-check.lua
* fxmanifest.lua
* web (entire folder)
# v3.2.1 (/advanced-garages/changelog/v3.2.1)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Fixes [#fixes]
* Failed to index nil value when storing vehicle in unregistered garage
* Failed to index nil value when driving vehicle out (server spawning only)
* Added `Config.Debug` logging
## Config [#config]
* Added `Config.Debug` (optional)
## Changed Files [#changed-files]
* client/cl-impound.lua
* client/cl-spawn.lua
* client/cl-vehicle.lua
* config/config.lua
* server/sv-garage.lua
* server/sv-society-garage.lua
* server/sv-spawn.lua
* server/sv-vehicle.lua
* shared/main.lua
* fxmanifest.lua
# v3.2.10 (/advanced-garages/changelog/v3.2.10)
## Tech Debt [#tech-debt]
* Support statebag strict mode
## Bugs [#bugs]
* Vehicle images fall back when Vehicle Studio is not installed
* Low end PC's can spawn multiple of same vehicles
## Changed Files [#changed-files]
* client/cl-garage.lua
* client/cl-spawn.lua
* framework/cl-functions.lua
* server/sv-garage.lua
* server/sv-impound.lua
* server/sv-spawn.lua
* fxmanifest.lua
* web (entire folder)
# v3.2.2 (/advanced-garages/changelog/v3.2.2)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Security [#security]
* Added new config option `Config.CheckVehicleModel`, to check vehicle model before storing
## Fixes [#fixes]
* More reliable extras re-application when driving out vehicle
* Fixed config callbacks returning server entity id
* Prevent duping vehicles when engine health is 0
* Added entity orphan for more reliable vehicle persistence
* Moved wasabi\_ambulance integration into pcall
## Changed Files [#changed-files]
* client/cl-garage.lua
* client/cl-impound.lua
* client/cl-interior.lua
* client/cl-spawn.lua
* client/cl-vehicle.lua
* config/config.lua
* framework/cl-functions.lua
* framework/esx/cl-esx.lua
* framework/sv-functions.lua
* package-lock.json
* server/sv-garage.lua
* server/sv-impound.lua
* server/sv-society-garage.lua
* server/sv-spawn.lua
* server/sv-vehicle.lua
* fxmanifest.lua
* web (entire folder)
# v3.2.3 (/advanced-garages/changelog/v3.2.3)
## Fixes [#fixes]
* Fix vehicles being locked on some servers when inside the garage interior
## Changed Files [#changed-files]
* client/cl-interior.lua
* client/cl-spawn.lua
* framework/cl-functions.lua
* package-lock.json
* fxmanifest.lua
# v3.2.4 (/advanced-garages/changelog/v3.2.4)
## Fixes [#fixes]
* Integrated Qbox's vehicle persistence & works alongside vehicle duplication protection
* Bumped npm dependencies (web)
## Changed Files [#changed-files]
* server/sv-garage.lua
* server/sv-vehicle.lua
* fxmanifest.lua
* web (entire folder)
# v3.2.5 (/advanced-garages/changelog/v3.2.5)
## Impound Vehicle Dupe Fix (Qbox) [#impound-vehicle-dupe-fix-qbox]
* I forgot to add the persistence exports to the impound... I am an idiot. This will fix vehicles respawning after being impounded and allowing people to dupe them. I asked Scorpion for his thoughts on this:
> "Average JG script" - Scorpion
## Exports [#exports]
* Added 2 new server exports: [https://docs.jgscripts.com/advanced-garages/commands-events-and-exports/exports](https://docs.jgscripts.com/advanced-garages/commands-events-and-exports/exports)
## Changed Files [#changed-files]
* client/cl-impound.lua
* server/sv-impound.lua
* server/sv-vehicle.lua
* fxmanifest.lua
# v3.2.6 (/advanced-garages/changelog/v3.2.6)
## Integrations [#integrations]
* Some changes to financing integration with JG Dealerships
## Changed Files [#changed-files]
* client/cl-garage.lua
* fxmanifest.lua
* web (entire folder)
# v3.2.7 (/advanced-garages/changelog/v3.2.7)
## Integrations [#integrations]
* Add support for JG Vehicle Studio (coming soon)
## Config [#config]
* Added `Config.UseJGVehicleStudio`
* Added `Config.JGVehicleStudioImageSet`
## Changed Files [#changed-files]
* client/cl-garage.lua
* client/cl-interior.lua
* client/cl-main.lua
* config/config.lua
* fxmanifest.lua
* web (entire folder)
# v3.2.8 (/advanced-garages/changelog/v3.2.8)
## JG Vehicle Studio Integration Fixes [#jg-vehicle-studio-integration-fixes]
* Send vehicle hash on ESX for reliable image lookup
* Fix issues with fallback images
## Changed Files [#changed-files]
* client/cl-garage.lua
* client/cl-interior.lua
* client/cl-main.lua
* config/config.lua
* fxmanifest.lua
* web (entire folder)
# v3.2.9 (/advanced-garages/changelog/v3.2.9)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Integrations [#integrations]
* RX Banking and Notify
* Add wasabi\_ambulance compatability for isDead
* Jota Vehicle Keys
* Restrict Jaksam's Keys Origin
* TGG-Banking
* LC-Fuel
* Fix rcore gangs integration
## Bugs [#bugs]
* Nearby players fetched before needed
* Support YFT liveries in menu & config
* Move impound time checking to the server to fix timezone issues
## Changed Files [#changed-files]
* client/cl-garage.lua
* client/cl-spawn.lua
* client/cl-vehicle.lua
* config/config.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* server/sv-garage.lua
* server/sv-impound.lua
* server/sv-society-garage.lua
* shared/main.lua
* fxmanifest.lua
* web (entire folder)
# v3 (/advanced-garages/changelog/v3)
Now requires ox\_lib to run - [click here](https://github.com/overextended/ox_lib/releases) to download it
## Features [#features]
* Entirely rewritten from the ground up, utilising ox\_lib, for performance, security & stability
* Now supports Qbox natively
* Rewritten vehicle spawning logic
* Enter garage interior (toggleable)
* Target (with NPC) & radial support
* Society garages can be assigned to multiple jobs/gangs
* Society garages can be unique, requiring transfers
* Max mods spawner vehicles
* Unique impounds - location can be chosen when impounding a vehicle
* Option for non unique public/private/house garages - no transfers required
* Configure impound time lengths
* Loads of other little fixes & improvements
### Integrations [#integrations]
* Integration with AdvancedParking + Brazzers-FakePlate out of the box
* Integration with MrNewbVehicleKeys & Renewed Vehicle Keys
## Installation [#installation]
This is an *entirely new resource*. DO NOT ATTEMPT TO REPLACE ANY INDIVIDUAL FILES! Reinstall the script from scratch.
## Database [#database]
This is an *entirely new resource*. DO NOT ATTEMPT TO REPLACE ANY INDIVIDUAL FILES! Reinstall the script from scratch.
## Config [#config]
Entirely new config - please reconfigure from scratch. There are some similarities, so there will likely be a lot of copying & pasting still.
## Video [#video]
Updated YouTube showcase (long overdue...) is coming soon!
# Commands (/advanced-garages/commands-events-and-exports/commands)
| Description | Command | Permissions |
| Impound vehicle | /iv | Default police; configurable in config/config.lua |
| Create private garage | /privategarages | Default realestate; configurable in config/config.lua |
| Move owned vehicle to job garage | /setjobvehicle [job_name] [min_job_grade] | Admins |
| Remove owned vehicle from job garage; give back to specified player ID | /removejobvehicle [new_player_owner_id] | Admins |
| Move owned vehicle to gang garage - QBCore only | /setgangvehicle [gang_name] [min_gang_grade] | Admins |
| Remove owned vehicle from gang garage; give back to specified player ID - QBCore only | /removegangvehicle [new_player_owner_id] | Admins |
| Change vehicle plate | /vplate | Admins |
| Delete owned vehicle from database | /dvdb | Admins |
| Add current vehicle to your garage - ESX only | /admincar | Admins |
| Return all vehicles marked as "left out" | /vreturn [plate] | |
# Events (/advanced-garages/commands-events-and-exports/events)
## Client [#client]
### Open garage [#open-garage]
Opens a specific garage, such as via a radial menu. If the `garageId` is a registered garage (i.e. specified in the config), it performs a location check automatically for security.
```lua
-- garageId: string
-- vehicleType?: "car" | "air" | "sea"
-- spawnCoords?: vector4 | Only needed for unknown/house garage integrations
TriggerEvent("jg-advancedgarages:client:open-garage", garageId, vehicleType, spawnCoords)
```
### Store current vehicle [#store-current-vehicle]
Stores the current vehicle in a garage. If the `garageId` is a registered garage (i.e. specified in the config), it performs a location check automatically for security.
```lua
--garageId: string
--garageVehicleType: "car" | "sea" | "air"
--return: boolean success
TriggerEvent("jg-advancedgarages:client:store-vehicle", garageId, garageVehicleType)
```
### Show impound form [#show-impound-form]
Shows the impound form (if you have permissions) - useful for a radial menu, item, or key bind.
```lua
TriggerEvent("jg-advancedgarages:client:show-impound-form")
```
### Show private garages manager [#show-private-garages-manager]
Opens the private garages dashboard, where you can create, edit or delete private garages.
```lua
TriggerEvent("jg-advancedgarages:client:show-private-garages-dashboard")
```
### Show change plate form [#show-change-plate-form]
Opens the change plate UI.
```lua
-- vehicle?: integer
TriggerEvent("jg-advancedgarages:client:show-vplate-form", vehicle)
```
## Server [#server]
### Register vehicle outside \[deprecated] [#register-vehicle-outside-deprecated]
This event has been deprecated and will be removed in a future release.
```lua
--plate: string
--netId: integer
TriggerEvent("jg-advancedgarages:server:register-vehicle-outside", plate, netId)
```
# Exports (/advanced-garages/commands-events-and-exports/exports)
## Server Exports [#server-exports]
### getAllGarages [#getallgarages]
Returns information on all garages.
```lua
-- server only
local garages = exports["jg-advancedgarages"]:getAllGarages()
--[[ example result:
{
{
name = "legion",
label = "Legion Square",
type = "public",
vehicle = "car",
blipName = "Public Garage",
blipColor = 3,
blipNumber = 357,
showBlip = true,
takeVehicle = vector3(215.9, -810.1, 30.7),
putVehicle = vector3(228.8, -800.1, 30.6),
spawnPoint = vector4(229.7, -810.0, 30.6, 157.5),
},
...
}
]]
```
| Returns | Type | Description |
| --------- | --------- | --------------------------------------------------- |
| `garages` | `table[]` | One entry per garage - see the example result shape |
### registerVehicleOutside [#registervehicleoutside]
Available in v3.2.5 and later
Registers a vehicle as outside the garage and currently spawned, so the vehicle cannot be respawned. This prevents vehicle duplication. Useful for integrating with scripts that also spawn vehicles from the garage, such as a valet.
```lua
-- server only
-- plate: string
-- netId: integer - the network ID of the vehicle (not the entity id!)
exports["jg-advancedgarages"]:registerVehicleOutside(plate, netId)
```
### deleteOutsideVehicle [#deleteoutsidevehicle]
Available in v3.2.5 and later
Deletes a vehicle via its plate if the vehicle is registered as outside within JG Advanced Garages - either via interacting with a garage directly or by using the `registerVehicleOutside` export.
```lua
-- server only
-- plate: string
exports["jg-advancedgarages"]:deleteOutsideVehicle(plate)
```
# Commands, Events & Exports (/advanced-garages/commands-events-and-exports)
# Brazzers-FakePlate (/advanced-garages/integrations/brazzers-fakeplate)
This guide is for v2 only. Brazzers-FakePlate is **integrated by default in v3!**\
\
This article will be removed shortly after the release of v3.
This code was provided to us by IgnoranceIsBliss.
It has been tested and works. It requires some code editing for garages and relies on **ox-lib**.
### Download the latest releases of ox-lib and Brazzers-FakePlate if you do not have them! [#download-the-latest-releases-of-ox-lib-and-brazzers-fakeplate--if-you-do-not-have-them-step]
### Modifiy the shared\_scripts Section of the Garage's fxmanifest.lua [#modifiy-the-shared_scripts-section-of-the-garages-fxmanifestlua-step]
> Replace the section with the one below.
```lua
shared_scripts {'config.lua', 'locales/*.lua', '@ox_lib/init.lua', 'main.lua', 'framework/main.lua'}
```
### Edit The Framework.Client.GetPlate Function in 'Framework/cl-functions.lua' [#edit-the-frameworkclientgetplate-function-in-frameworkcl-functionslua-step]
> Replace the `GetPlate` section with the code below.
```lua
function Framework.Client.GetPlate(vehicle)
if Config.Framework == "QBCore" then
local plate = QBCore.Functions.GetPlate(vehicle)
local originalPlate = lib.callback.await('brazzers-fakeplates:getPlateFromFakePlate', false, plate)
if originalPlate then plate = originalPlate end
return plate
elseif Config.Framework == "ESX" then
return ESX.Game.GetVehicleProperties(vehicle).plate
end
end
```
### Modify the jg-advancedgarages:client:TakeOutVehicle:config Event in 'config-cl.lua' [#modify-the-jg-advancedgaragesclienttakeoutvehicleconfig-event-in-config-cllua-step]
> Add the code below to the event.
```lua
local fakePlate = lib.callback.await('brazzers-fakeplates:getFakePlateFromPlate', false, vehicleDbData.plate)
if fakePlate then SetVehicleNumberPlateText(vehicle,fakePlate) end
```
### Add The Following to config-sv.lua [#add-the-following-to-config-svlua-step]
```lua
lib.callback.register('brazzers-fakeplates:getPlateFromFakePlate', function(source, fakeplate)
local result = MySQL.scalar.await('SELECT plate FROM player_vehicles WHERE fakeplate = ?', {fakeplate})
if result then
return result
end
end)
lib.callback.register('brazzers-fakeplates:getFakePlateFromPlate', function(source, plate)
local result = MySQL.scalar.await('SELECT fakeplate FROM player_vehicles WHERE plate = ?', {plate})
if result then
return result
end
end)
```
### Modify the Vehicle Key Functions to recognize the Fake Plate [#modify-the-vehicle-key-functions-to-recognize-the-fake-plate-step]
> Find `Framework.Client.VehicleGiveKeys(plate, vehicleEntity)` and add the code below after `if not DoesEntityExist(vehicleEntity) then return false end`
```lua
local fakePlate = lib.callback.await('brazzers-fakeplates:getFakePlateFromPlate', false, plate)
if fakePlate then plate = fakePlate end
```
Do the same for `Framework.Client.VehicleRemoveKeys(plate, vehicleEntity)`
```lua
local fakePlate = lib.callback.await('brazzers-fakeplates:getFakePlateFromPlate', false, plate)
if fakePlate then plate = fakePlate end
```
# Custom Fuel System (/advanced-garages/integrations/custom-fuel-system)
If we don't support your fuel system out of the box, you can add your own.
1. Go to the `framework` folder, and open `cl-functions.lua`
2. Find the `VehicleGetFuel` and `VehicleSetFuel` functions, which should look like this:
```lua
function Framework.Client.VehicleGetFuel(vehicle)
if (Config.FuelSystem == "LegacyFuel" or Config.FuelSystem == "ps-fuel" or Config.FuelSystem == "lj-fuel" or Config.FuelSystem == "cdn-fuel" or Config.FuelSystem == "hyon_gas_station") then
return exports[Config.FuelSystem]:GetFuel(vehicle)
elseif Config.FuelSystem == "ox_fuel" then
return GetVehicleFuelLevel(vehicle)
else
return 65 -- or set up custom fuel system here...
end
end
function Framework.Client.VehicleSetFuel(vehicle, fuel)
if (Config.FuelSystem == "LegacyFuel" or Config.FuelSystem == "ps-fuel" or Config.FuelSystem == "lj-fuel" or Config.FuelSystem == "cdn-fuel" or Config.FuelSystem == "hyon_gas_station") then
exports[Config.FuelSystem]:SetFuel(vehicle, fuel)
elseif Config.FuelSystem == "ox_fuel" then
Entity(vehicle).state.fuel = fuel
else
-- Setup custom fuel system here
end
end
```
3. Inside the `VehicleGetFuel` function, within the `else` block where it says\
\
`return 65 -- or set up custom fuel system here...`\
\
replace it with your custom **get fuel level** code. Remember to rename the vehicle entity variable to `vehicle` if your code snippet has it named differently.
4. Do the same inside the `VehicleSetFuel` function, replacing the line that says\
\
`-- Setup custom fuel system here`\
\
with the **set fuel level** function of your fuel script. The vehicle entity variable is named `vehicle` and the fuel level is named `fuel` - again, make sure to rename these if they're named differently in the snippet you're using.
5. In `config/config.lua`, set `Config.FuelSystem = "none"`.
# Custom Key System (/advanced-garages/integrations/custom-key-system)
If we don't support your key system out of the box, you can add your own.
1. Go to the `framework` folder, and open `cl-functions.lua`
2. Find the `VehicleGiveKeys` and `VehicleRemoveKeys` functions, which should look something like this:
```lua
function Framework.Client.VehicleGiveKeys(plate, vehicleEntity)
if not DoesEntityExist(vehicleEntity) then return false end
if Config.VehicleKeys == "qb-vehiclekeys" then
TriggerEvent("vehiclekeys:client:SetOwner", plate)
elseif Config.VehicleKeys == "jaksam-vehicles-keys" then
TriggerServerEvent("vehicles_keys:selfGiveVehicleKeys", plate)
elseif Config.VehicleKeys == "mk_vehiclekeys" then
exports["mk_vehiclekeys"]:AddKey(vehicleEntity)
elseif Config.VehicleKeys == "qs-vehiclekeys" then
local model = GetDisplayNameFromVehicleModel(GetEntityModel(vehicleEntity))
exports['qs-vehiclekeys']:GiveKeys(plate, model)
elseif Config.VehicleKeys == "wasabi_carlock" then
exports.wasabi_carlock:GiveKey(plate)
elseif Config.VehicleKeys == "cd_garage" then
TriggerEvent('cd_garage:AddKeys', plate)
elseif Config.VehicleKeys == "okokGarage" then
TriggerServerEvent("okokGarage:GiveKeys", plate)
elseif Config.VehicleKeys == "t1ger_keys" then
TriggerServerEvent('t1ger_keys:updateOwnedKeys', plate, true)
else
-- Setup custom key system here...
end
end
function Framework.Client.VehicleRemoveKeys(plate, vehicleEntity)
if not DoesEntityExist(vehicleEntity) then return false end
if Config.VehicleKeys == "qs-vehiclekeys" then
local model = GetDisplayNameFromVehicleModel(GetEntityModel(vehicleEntity))
exports['qs-vehiclekeys']:RemoveKeys(plate, model)
elseif Config.VehicleKeys == "wasabi_carlock" then
exports.wasabi_carlock:RemoveKey(plate)
elseif Config.VehicleKeys == "t1ger_keys" then
TriggerServerEvent('t1ger_keys:updateOwnedKeys', plate, false)
else
-- Setup custom key system here...
end
end
```
3. Inside the `VehicleGiveKeys` function, within the `else` block where it says\
\
`return 65 -- Setup custom key system here...`\
\
replace it with your custom **give keys** export. Also make sure you're providing the correct variables to the export based on the ones available:\
\
`plate` - Vehicle plate\
`vehicleEntity` - Physical vehicle entity\
\
If you need to get the vehicle model/spawn code, you can use:\
\
`local model = GetDisplayNameFromVehicleModel(GetEntityModel(vehicleEntity))`
4. If your key script can remove keys, do the same inside the `VehicleRemoveKeys` function, replacing the line that says\
\
`-- Setup custom key system here...`\
\
with the **remove keys** export of your key script. Again, make sure you're using the available variables, `plate` and `vehicleEntity`.
5. In `config/config.lua`, set `Config.VehicleKeys = "none"`.
# All Integrations (/advanced-garages/integrations)
## Key [#key]
## Phones [#phones]
| Script | Status | Integrating |
| yflip-phone | ✅ Officially Supported | Works out the box |
| LB Phone | ✅ Officially Supported | Works out the box - must be using v1.5.0 or later |
| okokPhone | ✅ Officially Supported | Works out the box - must be using v0.2.4 or later |
| GKS Phone | ✅ Officially Supported | Works out the box - must be using v2.0.6 or later |
| nPhone | ✅ Officially Supported | Works out the box - must be using v1.1.2 or later |
| Codem Phone | ✅ Officially Supported | Works out the box |
| JPR Phone | ✅ Officially Supported | Works out the box |
| 17mov_Phone | ✅ Officially Supported | Works out the box - must be using v1.0.22 or later |
| RoadPhone | ☑️ Supported | Guide (requires code): RoadPhone guide |
| qb-phone | ☑️ Supported | Slight modification to QBCore required: qb-phone guide |
| Quasar Smartphone Pro | ☑️ Supported | |
## Fuel [#fuel]
| Script | Status | Integrating |
| LegacyFuel | ✅ Officially Supported | Config.FuelSystem = "LegacyFuel" |
| ti_fuel | ✅ Officially Supported | Config.FuelSystem = "ti_fuel" |
| ps-fuel | ☑️ Supported | Config.FuelSystem = "ps-fuel" |
| lj-fuel | ☑️ Supported | Config.FuelSystem = "lj-fuel" |
| ox_fuel | ☑️ Supported | Config.FuelSystem = "ox_fuel" |
| cdn-fuel | ☑️ Supported | Config.FuelSystem = "cdn-fuel" |
| hyon_gas_station | ☑️ Supported | Config.FuelSystem = "hyon_gas_station" |
| okokGasStation | ☑️ Supported | Config.FuelSystem = "okokGasStation" |
| nd_fuel | ☑️ Supported | Config.FuelSystem = "nd_fuel" |
| myFuel | ☑️ Supported | Config.FuelSystem = "myFuel" |
| RCore Fuel | ☑️ Supported | Config.FuelSystem = "rcore_fuel" |
| Renewed Fuel | ☑️ Supported | Config.FuelSystem = "Renewed-Fuel" |
Using an unsupported fuel system? Read our guide: [Custom fuel system](/advanced-garages/integrations/custom-fuel-system)
## Key Systems [#key-systems]
| Script | Status | Integrating |
| qb-vehiclekeys | ✅ Officially Supported | Config.VehicleKeys = "qb-vehiclekeys" |
| MrNewbVehicleKeys | ✅ Officially Supported | Config.VehicleKeys = "MrNewbVehicleKeys" |
| jaksam-vehicles-keys | ☑️ Supported | Config.VehicleKeys = "jacksam-vehicles-keys" |
| mk_vehiclekeys | ☑️ Supported | Config.VehicleKeys = "mk_vehiclekeys" |
| wasabi_carlock | ☑️ Supported | Config.VehicleKeys = "wasabi_carlock" |
| cd_garage | ☑️ Supported | Config.VehicleKeys = "cd_garage" |
| okokGarage | ☑️ Supported | Config.VehicleKeys = "okokGarage" |
| t1ger_keys | ☑️ Supported | Config.VehicleKeys = "t1ger_keys" |
| tgiann-hotwire | ☑️ Supported | Config.VehicleKeys = "tgiann-hotwire" |
| qs-vehiclekeys | ☑️ Supported | Config.VehicleKeys = "qs-vehiclekeys" |
| Renewed Vehicle Keys | ☑️ Supported | Config.VehicleKeys = "Renewed" |
Using an unsupported key system? Read our guide: [Custom key system](/advanced-garages/integrations/custom-key-system)
## Banking [#banking]
| Script | Status | Integrating |
| qb-banking | ☑️ Supported | Config.Banking = "qb-banking" |
| qb-management | ☑️ Supported | Config.Banking = "qb-management" |
| esx_addonaccount | ☑️ Supported | Config.Banking = "esx_addonaccount" |
| Renewed Banking | ☑️ Supported | Config.Banking = "Renewed-Banking" |
| okokBanking | ☑️ Supported | Config.Banking = "okokBanking" |
| FD Banking | ☑️ Supported | Config.Banking = "fd_banking" |
## Gangs [#gangs]
| Script | Status | Integrating |
| qb-gangs | ☑️ Supported | Config.Gangs = "qb-gangs" |
| RCore Gangs | ☑️ Supported | Config.Gangs = "rcore_gangs" |
| t1ger gangs | 🟡 Work in Progress | |
## Notifications [#notifications]
| Script | Status | Integrating |
| Built-in | ✅ Officially Supported | Config.Notifications = "default" |
| ox_lib | ☑️ Supported | Config.Notifications = "ox_lib" |
| okokNotify | ☑️ Supported | Config.Notifications = "okokNotify" |
| ps-ui | ☑️ Supported | Config.Notifications = "ps-ui" |
## Text UI [#text-ui]
| Script | Status | Integrating |
| jg-textui | ✅ Officially Supported | Config.DrawText = "jg-textui" |
| qb-DrawText | ✅ Officially Supported | Config.DrawText = "qb-DrawText" |
| okokTextUI | ✅ Officially Supported | Config.DrawText = "okokTextUI" |
| ps-ui | ✅ Officially Supported | Config.DrawText = "ps-ui" |
| ox_lib | ☑️ Supported | Config.DrawText = "ox_lib" |
## Targets [#targets]
| Script | Status | Integrating |
| ox_target | ☑️ Supported | Config.Target = "ox_target" |
| qb-target | ☑️ Supported | Config.Target = "qb-target" |
## Housing [#housing]
## Mods [#mods]
## Persistent Vehicles [#persistent-vehicles]
# nolag_properties (/advanced-garages/integrations/nolag_properties)
In `nolag_properties/config.lua`, set the garage integration like this:
```lua title="nolag_properties/config.lua"
Garages = "jg-advancedgarages"
```
config.lua
# npwd (/advanced-garages/integrations/npwd)
Our app is currently only available for QBCore. ESX support is coming soon.
Get the phone here: [https://github.com/project-error/npwd](https://github.com/project-error/npwd)
JG Advanced Garages app: [https://github.com/JG-Scripts/npwd\_jg\_advancedgarages](https://github.com/JG-Scripts/npwd_jg_advancedgarages)
We've built a custom app for npwd that integrates with JG Advanced Garages.
## Requirements [#requirements]
* [npwd](https://github.com/project-error/npwd) (Requires NPWD 1.5 or newer)
* [qb-npwd](https://github.com/qbcore-framework/qb-npwd)
* [jg-advancedgarages](https://jgscripts.com/qb-esx-advanced-garages.html)
* [oxmysql](https://github.com/overextended/oxmysql)
## Installation [#installation]
1. Download the latest `npwd_jg_advancedgarages.zip` from the [releases page](https://github.com/JG-Scripts/npwd_jg_advancedgarages/releases). **Do not change the resource name!**
2. Unzip and add the resource to your server resources folder.
3. Ensure `npwd_jg_advancedgarages` **before** `npwd`
4. Add the app to NPWD's `config.json` in the `apps` section:
```json title="npwd/config.json"
"apps": ["npwd_jg_advancedgarages"]
```
## Credits [#credits]
Thanks to the developers of [npwd\_qb\_garage](https://github.com/npwd-community/npwd_qb_garage), whose app I originally forked to build this.
# okokDeleteVehicles (/advanced-garages/integrations/okokdeletevehicles)
Buy it here: [https://okok.tebex.io/package/5126470](https://okok.tebex.io/package/5126470)
Because JG Advanced Garages uses server-side vehicle spawning, you need to make a minor change to the script so it can delete vehicles.
1. Open `client.lua`
2. Replace **lines 92-95** with the following single line:
```lua
TriggerServerEvent('jg-advancedgarages:server:DeleteVehicleEntity', NetworkGetNetworkIdFromEntity(vehicle))
```
# ps-housing (/advanced-garages/integrations/ps-housing)
This guide only works with **Advanced Garages v3.0 or newer**.
Open `ps-housing/client/cl_property.lua` and replace these functions with the code below:
```lua
function Property:RegisterGarageZone()
if not next(self.propertyData.garage_data) then return end
if not (self.has_access or self.owner) then
return
end
local garageData = self.propertyData.garage_data
local garageName = string.format("property-%s-garage", self.property_id)
local data = {
takeVehicle = {
x = garageData.x,
y = garageData.y,
z = garageData.z,
w = garageData.h
},
type = "house",
label = self.propertyData.street .. self.property_id .. " Garage",
}
self.garageZone = lib.zones.box({
coords = vec3(garageData.x, garageData.y, garageData.z),
size = vector3(garageData.length + 5.0, garageData.width + 5.0, 3.5),
rotation = garageData.h,
debug = Config.DebugMode,
onEnter = function()
if IsPedInAnyVehicle(PlayerPedId(), true) then
lib.showTextUI('Press [E] to put the vehicle in the garage')
else
lib.showTextUI('Press [E] to open the garage')
end
end,
inside = function()
if IsControlJustReleased(0, 38) then
Wait(100)
inGarage = false
if IsPedInAnyVehicle(PlayerPedId(), true) then
TriggerEvent('jg-advancedgarages:client:store-vehicle', garageName, "car")
else
TriggerEvent('jg-advancedgarages:client:open-garage', garageName, "car", vec4(garageData.x, garageData.y, garageData.z, garageData.h))
end
end
end,
onExit = function()
lib.hideTextUI()
end,
})
end
function Property:UnregisterGarageZone()
if not self.garageZone then return end
self.garageZone:remove()
self.garageZone = nil
end
```
# qb-phone (/advanced-garages/integrations/qb-phone)
To fix the integrated qb-phone after removing qb-garages, and to see your owned vehicles on the garages app within the phone, make two modifications to the qb-phone source code.
## Go to file `qb-phone/client/main.lua` [#go-to-file-qb-phoneclientmainlua]
On **line 302**, replace the event name (or do a find and replace in your text editor), like in the screenshot below:
```lua title="qb-phone/client/main.lua"
-- find:
"qb-garage:server:GetPlayerVehicles"
-- replace with:
"jg-advancedgarages:server:GetVehiclesPhone"
```
## Go to file `qb-phone/server/main.lua` [#go-to-file-qb-phoneservermainlua]
Comment out lines **229 - 241** like in the screenshot below:
# RoadPhone (/advanced-garages/integrations/roadphone)
This code was provided to us by the developer of RoadPhone. Please notify RoadPhone so we can work together to get this documentation updated.
Buy it here: [https://fivem.roadshop.org/](https://fivem.roadshop.org/)
This integration makes the Valet functionality work with JG Advanced Garages.
Replace the contents of `roadphone/server/serverAPI/valet.lua` with the following (make sure you select the correct framework):
```lua
QBCore.Functions.CreateCallback("roadphone:valet:getCars", function(source, cb)
local xPlayer = QBCore.Functions.GetPlayer(source)
if not xPlayer then
return;
end
MySQL.Async.fetchAll("SELECT * FROM " .. Config.OwnedVehiclesTable .. " WHERE `citizenid` = @citizenid and type = @type and `impound` = @impound", {
['@impound'] = 0,
['@citizenid'] = xPlayer.PlayerData.citizenid,
['@type'] = "car"
}, function(result)
local cachedvehicles = {}
for i = 1, #result do
table.insert(cachedvehicles, {
plate = result[i].plate,
vehicle = result[i].vehicle, -- result[i].vehicle,
type = 'car',
hash = result[i].hash,
garage = result[i].garage_id,
stored = result[i].in_garage
})
end
cb(cachedvehicles)
end)
end)
QBCore.Functions.CreateCallback('roadphone:valet:loadVehicle', function(source, cb, plate)
local valetCheck = valetServerSideCheck(plate)
if valetCheck ~= false then
cb(false, valetCheck)
return;
end
MySQL.Async.fetchAll('SELECT * FROM ' .. Config.OwnedVehiclesTable .. ' WHERE `plate` = @plate', {
['@plate'] = plate
}, function(vehicle)
cb(vehicle)
end)
end)
QBCore.Functions.CreateCallback('roadphone:valet:checkMoney', function(source, cb)
local xPlayer = QBCore.Functions.GetPlayer(source)
if not xPlayer then
return;
end
if xPlayer.Functions.GetMoney('bank') > Config.ValetDeliveryPrice then
xPlayer.Functions.RemoveMoney('bank', Config.ValetDeliveryPrice, 'Car delivered')
local number = getNumberFromIdentifier(xPlayer.PlayerData.citizenid)
TriggerEvent("roadphone:addBankTransfer", number, 0, Lang:t('info.valet_car_delivered'), Config.ValetDeliveryPrice)
TriggerClientEvent("roadphone:sendNotification", source, {
apptitle = "APP_VALET_NAME",
title = "APP_VALET_CAR_ONTHEWAY",
img = "/public/img/Apps/valet.jpg"
})
discordLog("9807270", "Valet", xPlayer.PlayerData.name .. ' ' .. Lang:t('info.valet_car_delivered_2', { value = Config.ValetDeliveryPrice }), 'RoadPhone - Valet', nil, Cfg.ValetWebhook)
cb(true)
return;
else
TriggerClientEvent("roadphone:sendNotification", source, {
apptitle = "APP_VALET_NAME",
title = "APP_VALET_NOTENOUGHMONEY",
img = "/public/img/Apps/valet.jpg"
})
cb(false)
return;
end
end)
RegisterServerEvent("roadphone:valetCarSetOutside")
AddEventHandler("roadphone:valetCarSetOutside", function(plate)
MySQL.Async.execute('UPDATE '..Config.OwnedVehiclesTable..' SET `in_garage` = @in_garage WHERE `plate` = @plate', {
['@plate'] = plate,
['@in_garage'] = 0,
})
end)
```
```lua
ESX.RegisterServerCallback("roadphone:valet:getCars", function(source, cb)
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer then
return;
end
MySQL.Async.fetchAll("SELECT * FROM " .. Config.OwnedVehiclesTable .. " WHERE `owner` = @identifier and type = @type and `impound` = @impound", {
['@impound'] = 0,
['@identifier'] = xPlayer.identifier,
['@type'] = "car"
}, function(result)
local cachedvehicles = {}
for i = 1, #result do
local Garage = result[i].garage_id
local State = _U('valet_state_default')
table.insert(cachedvehicles, {
plate = result[i].plate,
vehicle = json.decode(result[i].vehicle),
garage = Garage,
state = State
})
end
cb(cachedvehicles)
end)
end)
ESX.RegisterServerCallback('roadphone:valet:loadVehicle', function(source, cb, plate)
local valetCheck = valetServerSideCheck(plate)
if valetCheck ~= false then
cb(false, valetCheck)
return;
end
MySQL.Async.fetchAll('SELECT * FROM ' .. Config.OwnedVehiclesTable .. ' WHERE `plate` = @plate', {
['@plate'] = plate
}, function(vehicle)
cb(vehicle)
end)
end)
ESX.RegisterServerCallback('roadphone:valet:checkMoney', function(source, cb)
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer then
return;
end
if xPlayer.getAccount('bank').money > Config.ValetDeliveryPrice then
xPlayer.removeAccountMoney('bank', Config.ValetDeliveryPrice)
local number = getNumberFromIdentifier(xPlayer.identifier)
TriggerEvent("roadphone:addBankTransfer", number, 0, _U('valet_car_delivered'), Config.ValetDeliveryPrice)
TriggerClientEvent("roadphone:sendNotification", source, {
apptitle = "APP_VALET_NAME",
title = "APP_VALET_CAR_ONTHEWAY",
img = "/public/img/Apps/valet.jpg"
})
discordLog("9807270", "Valet", xPlayer.getName() .. ' ' .. _U('valet_car_delivered_2', Config.ValetDeliveryPrice), 'RoadPhone - Valet', nil, Cfg.ValetWebhook)
cb(true)
return;
else
TriggerClientEvent("roadphone:sendNotification", source, {
apptitle = "APP_VALET_NAME",
title = "APP_VALET_NOTENOUGHMONEY",
img = "/public/img/Apps/valet.jpg"
})
cb(false)
return;
end
end)
RegisterServerEvent("roadphone:valetCarSetOutside")
AddEventHandler("roadphone:valetCarSetOutside", function(plate)
MySQL.Async.execute('UPDATE '..Config.OwnedVehiclesTable..' SET `in_garage` = @in_garage WHERE `plate` = @plate', {
['@plate'] = plate,
['@in_garage'] = 0,
})
return
end)
```
# Spawn Vehicles with ServerSetter (/advanced-garages/misc/spawn-vehicles-with-serversetter)
If you don't know what this means or don't care, don't touch this option at all. Leave it off.
If you know what this means, **I do recommend enabling it** but be aware you may experience reliability issues on more populated servers.
Having significant issues after enabling this? I beg you to just turn it off before opening a ticket with us.
HIGHLY recommended that you set `Config.DoNotSpawnInsideVehicle = false` if you decide to enable this.
Want to read my 'technical' rant about why server-sided vehicles are so frustrating in FiveM, or why this option even exists? See my blog:
# Why are you not using CreateVehicleServerSetter by default? (/advanced-garages/misc/why-are-you-not-using-createvehicleserversetter-by-default)
I wrote a rant about the woes of server-sided vehicles in FiveM a few years back. It now lives on my blog:
# Execution of native... SetDriftTyresEnabled error (/advanced-garages/troubleshooting/execution-of-native...-setdrifttyresenabled-error)
Example of the error
If you're getting this error, you're using a game version that is out of date. The code ESX is trying to use was introduced in game build 2372 ([https://docs.fivem.net/natives/?\_0x5AC79C98C5C17F05](https://docs.fivem.net/natives/?_0x5AC79C98C5C17F05)).
To fix this, go into your `server.cfg` and add the following line:
```txt
sv_enforcegamebuild 2944
```
# SQL error: foreign key constraint fails (/advanced-garages/troubleshooting/sql-error-foreign-key-constraint-fails)
"Cannot add or update a child row: a foreign key constraint fails...."
Example of the error
This is a common issue with some QBCore installations. When we set up job garages, we update the `citizenid` and set it to the name of the job. Some installations prevent this due to a database constraint.
To fix it, remove the constraint. Run the following SQL in your database:
```sql
ALTER TABLE player_vehicles DROP FOREIGN KEY FK_playervehicles_players
```
Some databases might use a different name from "FK\_playervehicles\_players", so make sure you change it to the one showing in your error.
Example of another foreign key name
In this example, the foreign key name is `player_vehicles_ibfk_1`, so the SQL query looks like this:
```sql
ALTER TABLE player_vehicles DROP FOREIGN KEY player_vehicles_ibfk_1
```
# ucrtbase.dll!strcmp crash (/advanced-garages/troubleshooting/ucrtbase.dll-strcmp-crash)
A common issue is the game crashing when you open a garage, with the following error:
This is because of a call to a FiveM native that is only available in newer versions of GTA 5. To fix this, add the following line anywhere in your `server.cfg`:
```txt
sv_enforcegamebuild 2944
```
# Client API (/dealerships/api/client-api)
## openShowroom [#openshowroom]
Open the dealership showroom UI for a player.
```lua
exports['jg-dealerships']:openShowroom(dealershipId, defaultVehicle, defaultColor)
```
| Parameter | Type | Required | Description |
| ---------------- | ------------- | -------- | ------------------------------------------------ |
| `dealershipId` | string | Yes | Dealership ID to open |
| `defaultVehicle` | string | No | Pre-select a vehicle by spawn code |
| `defaultColor` | number\|table | No | Pre-select a colour (paint index or `{r, g, b}`) |
## exitShowroom [#exitshowroom]
Close the showroom UI and clean up resources.
```lua
exports['jg-dealerships']:exitShowroom()
```
# Server API (/dealerships/api/server-api)
These exports are only available in Dealerships v2.1.1 or newer.
**Player Identifiers:** Some exports accept an `identifier` parameter. This is the player's `citizenid` (QBCore/Qbox) or `identifier` (ESX), depending on your framework.
**Return Pattern:** Mutation exports return `boolean, string?` - the boolean indicates success, and the optional string provides an error message on failure.
## Stock Management [#stock-management]
### incrementStock [#incrementstock]
Increment a vehicle's stock at a specific dealership.
```lua
local success, err = exports['jg-dealerships']:incrementStock(dealership, spawnCode, amount)
```
| Parameter | Type | Required | Description |
| ------------ | ------- | -------- | ------------------------------------- |
| `dealership` | string | Yes | Dealership ID |
| `spawnCode` | string | Yes | Vehicle spawn code (e.g. `"adder"`) |
| `amount` | integer | No | Amount to increment by (default: `1`) |
| Returns | Type | Description |
| --------- | --------- | --------------------------- |
| `success` | `boolean` | Whether the call succeeded |
| `error` | `string?` | Error message when it fails |
### decrementStock [#decrementstock]
Decrement a vehicle's stock at a specific dealership. Blocked if stock would go below 0.
```lua
local success, err = exports['jg-dealerships']:decrementStock(dealership, spawnCode, amount)
```
| Parameter | Type | Required | Description |
| ------------ | ------- | -------- | ------------------------------------- |
| `dealership` | string | Yes | Dealership ID |
| `spawnCode` | string | Yes | Vehicle spawn code |
| `amount` | integer | No | Amount to decrement by (default: `1`) |
| Returns | Type | Description |
| --------- | --------- | --------------------------- |
| `success` | `boolean` | Whether the call succeeded |
| `error` | `string?` | Error message when it fails |
### setStock [#setstock]
Set a vehicle's stock to an exact value at a specific dealership.
```lua
local success, err = exports['jg-dealerships']:setStock(dealership, spawnCode, stock)
```
| Parameter | Type | Required | Description |
| ------------ | ------- | -------- | ------------------------------ |
| `dealership` | string | Yes | Dealership ID |
| `spawnCode` | string | Yes | Vehicle spawn code |
| `stock` | integer | Yes | New stock value (must be >= 0) |
| Returns | Type | Description |
| --------- | --------- | --------------------------- |
| `success` | `boolean` | Whether the call succeeded |
| `error` | `string?` | Error message when it fails |
### getVehiclePrice [#getvehicleprice]
Get the price of a vehicle. If `dealershipId` is provided, returns the per-dealership price. Otherwise returns the base catalogue price.
```lua
local price, err = exports['jg-dealerships']:getVehiclePrice(spawnCode, dealershipId)
```
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ---------------------------------------- |
| `spawnCode` | string | Yes | Vehicle spawn code |
| `dealershipId` | string | No | Dealership ID for per-dealership pricing |
| Returns | Type | Description |
| ------- | --------------- | -------------------------------- |
| `price` | `number\|false` | The price, or `false` on failure |
| `error` | `string?` | Error message when it fails |
**Example:**
```lua
-- Get base catalog price
local price = exports['jg-dealerships']:getVehiclePrice("adder")
-- Get price at a specific dealership
local price = exports['jg-dealerships']:getVehiclePrice("adder", "pdm")
```
### getVehicleStock [#getvehiclestock]
Get the current stock level of a vehicle at a specific dealership.
```lua
local stock, err = exports['jg-dealerships']:getVehicleStock(spawnCode, dealershipId)
```
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ------------------ |
| `spawnCode` | string | Yes | Vehicle spawn code |
| `dealershipId` | string | Yes | Dealership ID |
| Returns | Type | Description |
| ------- | --------------- | -------------------------------- |
| `stock` | `number\|false` | The stock, or `false` on failure |
| `error` | `string?` | Error message when it fails |
## Finance [#finance]
### getPlayerFinancedVehicles [#getplayerfinancedvehicles]
Get all financed vehicles for a player.
```lua
local vehicles = exports['jg-dealerships']:getPlayerFinancedVehicles(identifier)
```
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------------------------ |
| `identifier` | string | Yes | Player identifier (citizenid / identifier) |
| Returns | Type | Description |
| ---------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `vehicles` | `table[]` | Array of financed vehicle records. Each record contains `plate`, `financed`, `finance_data` (JSON string), and all other columns from the vehicles table. |
**Example:**
```lua
local vehicles = exports['jg-dealerships']:getPlayerFinancedVehicles("ABC12345")
for _, vehicle in ipairs(vehicles) do
local financeData = json.decode(vehicle.finance_data)
print(vehicle.plate, financeData.payments_complete .. "/" .. financeData.total_payments)
end
```
### getFinanceByPlate [#getfinancebyplate]
Get finance details for a specific vehicle by its plate.
```lua
local vehicle = exports['jg-dealerships']:getFinanceByPlate(plate)
```
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------- |
| `plate` | string | Yes | Vehicle plate |
| Returns | Type | Description |
| --------- | -------- | ------------------------------------------------------------------- |
| `vehicle` | `table?` | The vehicle record if financed, or `nil` if not found/not financed. |
### makeFinancePayment [#makefinancepayment]
Programmatically make a finance payment for an online player. Deducts money from their account.
```lua
local success, err = exports['jg-dealerships']:makeFinancePayment(src, plate)
```
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | --------------------------------- |
| `src` | number | Yes | Player server ID (must be online) |
| `plate` | string | Yes | Vehicle plate |
| Returns | Type | Description |
| --------- | --------- | --------------------------- |
| `success` | `boolean` | Whether the call succeeded |
| `error` | `string?` | Error message when it fails |
> **Note:** This deducts the recurring payment amount from the player's account using the currency configured for the finance. The player must be online.
### getPlayerFinanceCount [#getplayerfinancecount]
Get the number of active financed vehicles a player has.
```lua
local count = exports['jg-dealerships']:getPlayerFinanceCount(identifier)
```
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------------------------ |
| `identifier` | string | Yes | Player identifier (citizenid / identifier) |
| Returns | Type | Description |
| ------- | --------- | ---------------------------- |
| `count` | `integer` | Number of financed vehicles. |
## Dealership Balance [#dealership-balance]
### getDealershipBalance [#getdealershipbalance]
Get the current balance of a dealership's account.
```lua
local balance, err = exports['jg-dealerships']:getDealershipBalance(dealershipId)
```
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ------------- |
| `dealershipId` | string | Yes | Dealership ID |
| Returns | Type | Description |
| --------- | --------------- | ---------------------------------- |
| `balance` | `number\|false` | The balance, or `false` on failure |
| `error` | `string?` | Error message when it fails |
> **Note:** When using framework jobs, this returns the society/job account balance. Otherwise it returns the `balance` column from `dealership_locations`.
### addDealershipBalance [#adddealershipbalance]
Add funds to a dealership's account.
```lua
local success, err = exports['jg-dealerships']:addDealershipBalance(dealershipId, amount)
```
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | --------------------------- |
| `dealershipId` | string | Yes | Dealership ID |
| `amount` | number | Yes | Amount to add (must be > 0) |
| Returns | Type | Description |
| --------- | --------- | --------------------------- |
| `success` | `boolean` | Whether the call succeeded |
| `error` | `string?` | Error message when it fails |
### removeDealershipBalance [#removedealershipbalance]
Remove funds from a dealership's account.
```lua
local success, err = exports['jg-dealerships']:removeDealershipBalance(dealershipId, amount)
```
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ------------------------------ |
| `dealershipId` | string | Yes | Dealership ID |
| `amount` | number | Yes | Amount to remove (must be > 0) |
| Returns | Type | Description |
| --------- | --------- | --------------------------- |
| `success` | `boolean` | Whether the call succeeded |
| `error` | `string?` | Error message when it fails |
## Employees [#employees]
### isEmployee [#isemployee]
Check if a player is an employee at a dealership.
```lua
local role = exports['jg-dealerships']:isEmployee(src, dealershipId)
```
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ---------------- |
| `src` | number | Yes | Player server ID |
| `dealershipId` | string | Yes | Dealership ID |
| Returns | Type | Description |
| ------- | --------------- | ------------------------------------------------------------------------- |
| `role` | `string\|false` | Role name (e.g. `"manager"`, `"salesman"`) or `false` if not an employee. |
> **Note:** When using framework jobs, this checks the player's current job/grade. When using the built-in employee system, this checks the `dealership_employees` table.
**Example:**
```lua
local role = exports['jg-dealerships']:isEmployee(source, "pdm")
if role then
print("Player is a " .. role .. " at PDM")
end
```
### hasPermission [#haspermission]
Check if a player has a specific permission at a dealership.
```lua
local allowed = exports['jg-dealerships']:hasPermission(src, dealershipId, permission)
```
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ------------------- |
| `src` | number | Yes | Player server ID |
| `dealershipId` | string | Yes | Dealership ID |
| `permission` | string | Yes | Permission to check |
| Returns | Type | Description |
| --------- | --------- | --------------------- |
| `allowed` | `boolean` | See description above |
**Available Permissions:**
| Permission | Description |
| ------------------ | ------------------------------------------------------- |
| `ADMIN` | Full access to everything |
| `MANAGE_EMPLOYEES` | Hire, fire, and change employee roles |
| `MANAGE_INVENTORY` | Order vehicles, manage stock, display vehicles, pricing |
| `MANAGE_FINANCES` | Access dealership bank and settings |
| `SELL` | Perform direct sales and test drives |
| `DELIVER` | Complete trucking delivery missions |
| `VIEW_RECORDS` | View sales and order history |
### getEmployees [#getemployees]
Get all employees at a dealership.
```lua
local employees = exports['jg-dealerships']:getEmployees(dealershipId)
```
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ------------- |
| `dealershipId` | string | Yes | Dealership ID |
| Returns | Type | Description |
| ----------- | --------- | ---------------------------------------------------------------------------------- |
| `employees` | `table[]` | Array of employee records with `id`, `identifier`, `dealership`, `role`, `joined`. |
## Locations [#locations]
### getDealerships [#getdealerships]
Get all dealership locations.
```lua
local dealerships = exports['jg-dealerships']:getDealerships()
```
| Returns | Type | Description |
| ------------- | ------------ | ----------------------------------------------------------------------- |
| `dealerships` | `Location[]` | Array of all dealership location objects with their full configuration. |
**Example:**
```lua
local dealerships = exports['jg-dealerships']:getDealerships()
for _, dealership in ipairs(dealerships) do
print(dealership.id, dealership.name, dealership.type)
end
```
### getDealership [#getdealership]
Get a specific dealership by its ID.
```lua
local dealership = exports['jg-dealerships']:getDealership(dealershipId)
```
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | -------------------- |
| `dealershipId` | string | Yes | Dealership ID (UUID) |
| Returns | Type | Description |
| ------------ | ----------------- | ------------------------------------------------------- |
| `dealership` | `Location\|false` | The dealership location object, or `false` if not found |
## Showroom [#showroom]
### getShowroomVehicles [#getshowroomvehicles]
Get all vehicles available in a dealership's showroom (with current stock and per-dealership pricing).
```lua
local vehicles = exports['jg-dealerships']:getShowroomVehicles(dealershipId)
```
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ------------- |
| `dealershipId` | string | Yes | Dealership ID |
| Returns | Type | Description |
| ---------- | ------------------ | ----------------------------------------------------------------------------------- |
| `vehicles` | `Vehicle[]\|false` | Showroom vehicles with stock and pricing, or `false` if the dealership is not found |
Each vehicle contains: `spawn_code`, `brand`, `model`, `category`, `price` (per-dealership), `stock`, `unlimited_stock`, `global_stock_limit`.
**Example:**
```lua
local vehicles = exports['jg-dealerships']:getShowroomVehicles("pdm")
if vehicles then
for _, v in ipairs(vehicles) do
print(v.spawn_code, v.price, v.stock)
end
end
```
## Coupons [#coupons]
### createCoupon [#createcoupon]
Programmatically create a coupon for a dealership.
```lua
local coupon, err = exports['jg-dealerships']:createCoupon(dealershipId, data)
```
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------------- |
| `dealershipId` | string | Yes | Dealership ID |
| `data` | table | Yes | Coupon configuration (see below) |
**Coupon Data:**
| Field | Type | Required | Description |
| ----------------------- | --------- | -------- | --------------------------------------------------------------------- |
| `code` | string | No | Custom coupon code. Auto-generated if omitted (`XXXX-XXXX` format) |
| `discount_type` | string | Yes | `"percent"` or `"fixed"` |
| `discount_value` | number | Yes | Discount amount (percentage or fixed value) |
| `max_uses` | integer | No | Maximum total uses |
| `per_player_limit` | integer | No | Maximum uses per player |
| `expiry_date` | integer | No | Expiry timestamp in milliseconds |
| `vehicle_restrictions` | string\[] | No | Array of spawn codes this coupon is valid for |
| `category_restrictions` | string\[] | No | Array of categories this coupon is valid for |
| `allow_finance` | boolean | No | Whether coupon can be used with financed purchases (default: `false`) |
| Returns | Type | Description |
| -------- | -------------- | -------------------------------------------------------------------------- |
| `coupon` | `table\|false` | The created coupon object (with `id`, `code`, etc.), or `false` on failure |
| `err` | `string?` | Error message when creation fails |
**Example:**
```lua
local coupon, err = exports['jg-dealerships']:createCoupon("pdm", {
discount_type = "percent",
discount_value = 15,
max_uses = 100,
per_player_limit = 1,
category_restrictions = { "super", "sports" },
allow_finance = false
})
if coupon then
print("Created coupon: " .. coupon.code)
end
```
### validateCoupon [#validatecoupon]
Validate a coupon code without consuming it. Useful for checking eligibility before a purchase.
```lua
local result = exports['jg-dealerships']:validateCoupon(code, dealershipId, spawnCode, category, isFinanced)
```
| Parameter | Type | Required | Description |
| -------------- | ------- | -------- | -------------------------------------------------- |
| `code` | string | Yes | Coupon code |
| `dealershipId` | string | Yes | Dealership ID |
| `spawnCode` | string | No | Vehicle spawn code (for vehicle restriction check) |
| `category` | string | No | Vehicle category (for category restriction check) |
| `isFinanced` | boolean | No | Whether the purchase is financed |
| Returns | Type | Description |
| ---------------- | ------- | ----------------------------------------------------------------------------------------------- |
| `valid` | boolean | Whether the coupon is valid |
| `message` | string? | Error message if invalid |
| `discount` | number? | Calculated discount amount (if `spawnCode` provided and vehicle has a price at this dealership) |
| `discount_type` | string? | `"percent"` or `"fixed"` (if valid) |
| `discount_value` | number? | Raw discount value (if valid) |
> **Note:** This does NOT consume the coupon or increment its usage count. It is a read-only check.
## Sales History [#sales-history]
### getSalesHistory [#getsaleshistory]
Get recent sales records for a dealership.
```lua
local sales = exports['jg-dealerships']:getSalesHistory(dealershipId, limit)
```
| Parameter | Type | Required | Description |
| -------------- | ------- | -------- | ------------------------------------------------- |
| `dealershipId` | string | Yes | Dealership ID |
| `limit` | integer | No | Max records to return (default: `50`, max: `500`) |
| Returns | Type | Description |
| ------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sales` | `table[]` | Array of sale records ordered by most recent first. Each record contains `id`, `dealership`, `vehicle`, `plate`, `player`, `seller`, `purchase_type`, `paid`, `owed`. |
### getTotalSales [#gettotalsales]
Get the total revenue (sum of all payments received) for a dealership.
```lua
local total = exports['jg-dealerships']:getTotalSales(dealershipId)
```
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ------------- |
| `dealershipId` | string | Yes | Dealership ID |
| Returns | Type | Description |
| ------- | -------- | --------------------- |
| `total` | `number` | Total revenue amount. |
# Changelog (/dealerships/changelog)
# v1.0.1 (/dealerships/changelog/v1.0.1)
## Changelog [#changelog]
* Disable finance now actually disables finance :sweat\_smile:
* Configurable events when test driving a vehicle or purchasing a vehicle (config-cl.lua, config-sv.lua)
* Added 5 new translations (huge :orange\_heart: to everyone in #🌎-translations)
* Fix bug with empty list of nearby players
* Added remove keys function for test drives, pre setup for qs-vehiclekeys
* Fixed qs-vehiclekeys give keys event
* Fixed constraint SQL error when deleting vehicle from /dealeradmin
* Fixed ability to spam test drive and spawn multiple vehicles
* Fixed test drive exploit + added spawn protection
## Files Changed [#files-changed]
```
/client (whole folder)
/server (whole folder)
/framework (whole folder)
/web (whole folder)
/locales (whole folder)
+ config-cl.lua
+ config-sv.lua
```
# v1.0.2 (/dealerships/changelog/v1.0.2)
## Changelog [#changelog]
* Fix drowing/dying/glitches when in showroom
* Plate index is now always white when purchasing vehicle
* Vehicle keys, modifications and events all occur after vehicle is registered in DB
* Fix text in UI from highlighting when clicking buttons too fast
* Customisable permission groups (`sv-functions.lua` -> `Framework.Server.IsAdmin`)
* Adding of vehicle to DB is open source to aid with standalone usage
* Minimap and HUD manipulation is open source (`cl-functions.lua` -> `Framework.Client.ToggleHud`)
* Fix page resetting when editing vehicles in /dealeradmin
* Loading state to prevent UI lag when model is loading
* Limit number of active financed vehicles per player `Config.MaxFinancedVehiclesPerPlayer`
* Discord Webhooks for 5 different event types
* Fix vehicle ordering time exploit
* Fixed Configurator category bugs
## Files Changed [#files-changed]
```
/client (whole folder)
/server (whole folder)
/framework (whole folder)
/web (whole folder)
/locales (whole folder)
+ config-cl.lua
+ config-sv.lua
```
# v1.0.3 (/dealerships/changelog/v1.0.3)
## Changelog [#changelog]
* :warning: Fix finance bug where final payment would crash and repeatedly charge player
* Remove rogue prints
* `Config.Logging` turned off by default
## Files Changed/Upgrading [#files-changedupgrading]
Replace the following files. No config changes!
* `server/sv-finance.lua`
* `server/sv-main.lua`
# v1.1.1 (/dealerships/changelog/v1.1.1)
## Changelog [#changelog]
* Fix: Direct sale number of payments/down payment not applying
* Fix: SQL error when visiting Employees tab
* Fix: Vehicle sales not working (model name being retrieved incorrectly)
* `dealershipId` is now available in `showroom-pre-check` functions
* Configurator is now working with imports from pre \< v1.1 config files
## Files Changed [#files-changed]
* `client/cl-showroom.lua`
* `server/sv-management.lua`
* `config-cl.lua`
* `config-sv.lua`
* `client/cl-sell-vehicle.lua`
* `server/sv-sell-vehicle.lua`
# v1.1.2 (/dealerships/changelog/v1.1.2)
## Changelog [#changelog]
* Fix: Vehicle sell issue
* Toggleable vehicle sell and percentage configurable for each dealership
* Added exposed function for vehicle sell (example: running an event after the vehicle is sold) configurable in `config-cl.lua`
### New Config Options within Dealership: [#new-config-options-within-dealership]
* Add these config options into each dealership
```lua
enableSellVehicle = true,
sellVehiclePercent = 0.6, -- 60% of vehicle sale price
```
### Configurator Updates [#configurator-updates]
Configurator has the marker issue fixed, new available locales added + updated for new sell vehicle config options with each Dealership.
### Files Changed [#files-changed]
* `client/cl-blips.lua`
* `client/cl-sell-vehicle.lua`
* `server/sv-sell-vehicle.lua`
* `server/sv-main.lua`
* `config-cl.lua`-> new event (`"jg-dealerships:client:sell-vehicle:config"`)
# v1.1.3 (/dealerships/changelog/v1.1.3)
## Changelog [#changelog]
* Fixed ESX blips/textUI when changing jobs
* Fixed plate is not being added to props
* Opened discord webhooks/URLs function & moved from config.lua to server/sv-webhooks.lua
* Fixed /directsale UI to reflect the values set in config.lua
* Fixed issue with qb-management & qb-banking update
* Fixed server queries being run in background (optimisation)
* Merged Keys & Fuel functions from garages
* Fixed Vehicles randomly getting deleted after 1 or 2 payments
* Fix withdraw/deposit exploit
Remove Webhook lines in config.lua
Webhook URL available in server/sv-webhooks.lua
### Files Changed [#files-changed]
* All Files Changed except config.lua
* New file added server/sv-webhooks.lua
# v1.1.4 (/dealerships/changelog/v1.1.4)
## Changelog [#changelog]
* Fixed missing hash keys
* Fixed test drive vehicle not deleting
* Fixed finance timer not counting down
* Fixed webhook crash on test drive
### Files Changed [#files-changed]
* web folder (entire)
* client/cl-test-drive.lua
* server/sv-admin.lua
* server/sv-webhooks.lua
# v1.1.5 (/dealerships/changelog/v1.1.5)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
**?? Highly recommended update, fixes a lot of known bugs & exploits**
## Features [#features]
* Players are instanced on test drives by default (can be disabled)
* Salesman's name is shown in the "Sales" tab of the dealer management UI (when using direct sale)
* Spawn codes are validated when adding a new vehicle to /dealeradmin
* Option to hide JG Scripts watermark
* Update checker
## Fixes [#fixes]
* Vehicle spawning updated to match Advanced Garages
* Fail-safes to prevent getting stuck on the dreaded "Purchasing..." black screen
* VehicleGeneratePlate will time out if no plate is found + improved code [with new options](https://docs.jgscripts.com/dealerships/plate-format)
* Fix multiple possible exploits where buttons could be spam-clicked or unauthorised events accessed
* Vehicles with no seats can be purchased with crashing (trailers, etc.)
* Vehicle sell prices are no longer based off the global price, and instead the dealer price
* Refresh blips & text UI when job/gang changes
* Prevent game crashing/memory leaks when spam clicking vehicles in showroom
* Prevent players from becoming trapped in showroom (while server is online)
* Fix /directsale breaking when vector4 is used for `openShowroom`
* `client:sell-vehicle:config` now contains `DeleteVehicle()` so you can run custom code before the vehicle entity is deleted
* Purchase vehicle events re-written and now open source
* Fixed webhook incorrectly displaying all vehicles were purchased on finance
* Fixed bug when setting immediate deliveries \[`Config.VehicleOrderTime = 0`]
* Keys are now removed when selling a vehicle
* Remove a significant number of unnecessary SQL queries
* Players are now instanced when entering the showroom
* Fix minor UI bug where tables would have slightly jagged lines
## Config Changes [#config-changes]
```
Config.DisplayVehiclesHidePurchasePrompt = false
Config.DisplayVehiclesPurchasePromptDistance = 1
Config.TestDriveNotInBucket = false
```
## SQL [#sql]
You **MUST** run the following SQL when you update:
```sql
ALTER TABLE dealership_sales ADD COLUMN seller varchar(255);
```
## New Locales [#new-locales]
```
spawnCodeInvalid = "Spawn code invalid"
```
## Changed Files [#changed-files]
* client/cl-admin.lua
* client/cl-blips.lua
* client/cl-display-vehicles.lua
* client/cl-main.lua
* client/cl-purchase.lua
* client/cl-sell-vehicle.lua
* client/cl-showroom.lua
* client/cl-test-drive.lua
* config-cl.lua
* framework/cl-functions.lua
* framework/esx/cl-esx.lua
* framework/main.lua
* framework/qb/cl-qb.lua
* framework/sv-functions.lua
* fxmanifest.lua
* run-esx.sql
* run-qb.sql
* server/sv-admin.lua
* server/sv-direct-sales.lua
* server/sv-employees.lua
* server/sv-finance.lua
* server/sv-main.lua
* server/sv-management.lua
* server/sv-purchase.lua
* server/sv-sell-vehicle.lua
* server/sv-showroom.lua
* server/sv-test-drive.lua
* server/sv-version-check.lua
* web (entire folder)
* locales (translations modified)
# v1.1.6 (/dealerships/changelog/v1.1.6)
A few post-update fixes and discoveries from the community ?? appreciate you all! No configuration changes, just a few files to replace, nice and easy!
## Fixes [#fixes]
* Edit button disabled in /dealeradmin when editing a vehicle
* Fix "Could not get society balance" bug
* Fix issue where you would end up stuck in the showroom interior when spamming E
* `server:purchase-vehicle:config` now actually sends the `vehNetId`
## Changed Files [#changed-files]
* client/cl-purchase.lua
* client/cl-showroom.lua
* config-sv.lua
* framework/cl-functions.lua
* server/sv-main.lua
* server/sv-purchase.lua
* server/sv-showroom.lua
* web (entire folder)
# v1.1.7 (/dealerships/changelog/v1.1.7)
## Fixes [#fixes]
* Fix getting stuck on a test drive (mostly an ESX issue)
* Handle scenario where vehicle model does not exist in the showroom
## Changed Files [#changed-files]
* client/cl-showroom.lua
* client/cl-test-drive.lua
* framework/cl-functions.lua
* server/sv-showroom.lua
* server/sv-test-drive.lua
# v1.1.8 (/dealerships/changelog/v1.1.8)
I missed an `s` when updating a variable... a single letter was missing (causing players to be teleported back to the dealership when relogging)
Me after today: [https://www.youtube.com/watch?v=HjPgdhRsGIQ](https://www.youtube.com/watch?v=HjPgdhRsGIQ)
## Changed Files [#changed-files]
* server/sv-showroom.lua
# v1.1.9 (/dealerships/changelog/v1.1.9)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Fixes [#fixes]
* You no longer go invisible/get stuck in another realm (bucketing bugs/race condition)
* Vehicle props not updating after purchase on some DB configurations
* Vehicles spawning under the map when using server-sided spawning
* Fixed crash when purchasing a vehicle for a society
* Fixed issue where commission wouldn't be paid after direct sale
* Optimised complex `vehicle_sales` SQL query
## Translations [#translations]
* Improved Spanish translation
* Added missing direct sale translations
* "Dealer" is now "Sales rep"
## Changed Files [#changed-files]
* client/cl-direct-sales.lua
* client/cl-management.lua
* client/cl-purchase.lua
* client/cl-showroom.lua
* client/cl-test-drive.lua
* config-cl.lua
* framework/sv-functions.lua
* server/sv-direct-sales.lua
* server/sv-finance.lua
* server/sv-main.lua
* server/sv-management.lua
* server/sv-purchase.lua
* server/sv-showroom.lua
* server/sv-test-drive.lua
* web (entire folder)
* locales (translations modified)
# v1.1 (/dealerships/changelog/v1.1)
## Changelog [#changelog]
* Dealerships can hire employees
* Employees can sell vehicles directly to customers; showroom can be disabled
* Sell vehicle back to the dealership
* Optional markers so dealership locations are easier to find
* Hide blips (set `hideBlip = true`) within the dealership config
* Restrict access to Dealership based on job or gang
* Open callback on client & server for restricting showroom access
* "Restore showroom" button for resetting display vehicles if they get messed up
* Vehicle sales now show name in Dealership Management
* Toggle financing per dealership
* Set new vehicle plate patterns + fix bug where plate would exist in DB
* Fix: okokTextUI spamming sound (fixed text ui being constantly re-rendered)
* Fix: Health being restored when exiting the dealership/ending test drive
* Fix: SQL error when updating a vehicle in /dealeradmin
* Fix: Colour changing after vehicle purchase
* Fix: Financing bugs
## Upgrade Instructions [#upgrade-instructions]
1. Replace **all** files
2. Run the following SQL code in your database (works for both QB & ESX):
```sql
ALTER TABLE `dealership_data` ADD IF NOT EXISTS `employee_commission` INT NOT NULL DEFAULT '10';
CREATE TABLE IF NOT EXISTS `dealership_employees` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`identifier` varchar(255) NOT NULL,
`dealership` varchar(255) NOT NULL,
`role` varchar(100) NOT NULL,
`joined` datetime NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `fk_employees_dealership` (`dealership`),
CONSTRAINT `fk_employees_dealership` FOREIGN KEY (`dealership`) REFERENCES `dealership_data` (`name`)
);
```
## New Chat Command [#new-chat-command]
All dealership employees (any rank) get access to the new `/directsale` chat command. This brings up a menu and shows a tablet emote in-game, allowing the employee to sell a vehicle directly to a nearby player. The nearby player will have to approve and sign to finalise the purchase.
This will only work if they are (1) employed by that dealership (2) close enough to the "showroom" coords location. The radius can be configured via `directSaleDistance` within the dealership location within `config.lua`
## Config File Changes [#config-file-changes]
You should probably use the Configurator: [https://configurator.jgscripts.com](https://configurator.jgscripts.com)
```lua
Config.PlateFormat = "1AA111AA" -- 1 = number, A = letter (max 8 characters)
Config.SellVehiclePrompt = "[E] Sell Vehicle"
Config.SellVehicleKeyBind = 38
Config.EnableSellVehicle = true -- Vehicles are sold back to the dealerships - they must pay for the vehicle but receive the vehicle as stock
Config.SellVehiclePercent = 0.6 -- 60% of current sale price
```
### New Config Options within Dealership: [#new-config-options-within-dealership]
```lua
sellVehicle = vector3(-27.89, -1082.1, 26.64), -- sell vehicle location
enableFinance = true,
hideMarkers = false,
markers = { id = 21, size = { x = 0.3, y = 0.3, z = 0.3 }, color = { r = 255, g = 255, b = 255, a = 120 }, bobUpAndDown = 0, faceCamera = 0, rotate = 1, drawOnEnts = 0 },
showroomJobWhitelist = {}, -- format: showroomJobWhitelist = { mechanic = {1, 2, 3} }
showroomGangWhitelist = {}, -- (QB only) format: showroomGangWhitelist = { lostmc = {1, 2, 3} }
disableShowroomPurchase = false, -- if you want to use /directsale only
directSaleDistance = 50, -- how far away employees can use /directsale
job = "cardealer", -- Owned dealerships only; job employees get within QB/ESX
```
## Employees [#employees]
You can now hire employees via the Dealership Management > Employees tab.
Employees can be one of three ranks: Manager, Supervisor or Sales. You cannot change this, but you can configure how this aligns to ranks for the job in your framework via `framework/sv-functions.lua`
Here is the permissions breakdown:
# v1.2.1 (/dealerships/changelog/v1.2.1)
## Fixes [#fixes]
* Backwards compatibility with v1.1.X & configurator config files
* Fix "entity owner is -1" error when using server-sided spawning
* Cleanup memory after streaming animations & display vehicle models
* Fix ti\_fuel integration
* Fix /directsale colour error
## Important Note [#important-note]
If you are upgrading from *before v1.2*, you need to perform a *full re-install* of the script. The entire file & folder structure has changed.
## Changed Files [#changed-files]
* client/cl-display-vehicles.lua
* client/cl-locations.lua
* client/cl-main.lua
* client/cl-spawn.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* server/sv-showroom.lua
* web (entire folder)
# v1.2.2 (/dealerships/changelog/v1.2.2)
## Fixes [#fixes]
* Fix finance bug in Qbox (causing could not execute native error)
* Fix `vehicle` cannot be nil error in ESX when purchasing a vehicle
* Detect whether `shop = "x"` exists in Qbox shared when importing
## Changed Files [#changed-files]
* framework/sv-functions.lua
* fxmanifest.lua
* server/sv-admin.lua
* server/sv-spawn.lua
# v1.2.3 (/dealerships/changelog/v1.2.3)
## Optional update [#optional-update]
Increases the timeout for all model requests to 60secs so it doesn't time out loading your high poly TOS breaking vehicles ??
## Changed Files [#changed-files]
* client/cl-display-vehicles.lua
* client/cl-showroom.lua
* client/cl-spawn.lua
# v1.2.4 (/dealerships/changelog/v1.2.4)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Fixes [#fixes]
* Ped sometimes not warped for test drive (server-sided spawning only)
* Dealership owners/employees not recognised after server restart
* Fix notification title typo
* Trim archetype names before DB entry
* Brazzers-FakePlate support; fixing sale exploit
* Webhook incorrectly displaying finance status of new purchase
* Fix commission float issue (7.00000000001%)
## QOL [#qol]
* Added direct sale client event `"jg-dealerships:client:direct-sale"`
* mx\_carkeys support
## Changed Files [#changed-files]
* client/cl-direct-sales.lua
* client/cl-display-vehicles.lua
* client/cl-locations.lua
* client/cl-purchase.lua
* client/cl-sell-vehicle.lua
* client/cl-spawn.lua
* client/cl-test-drive.lua
* config/config.example.lua
* framework/cl-functions.lua
* framework/esx/cl-esx.lua
* framework/qb/cl-qb.lua
* framework/qbx/cl-qbx.lua
* framework/sv-functions.lua
* server/sv-admin.lua
* server/sv-finance.lua
* server/sv-initsql.lua
* server/sv-main.lua
* server/sv-management.lua
* server/sv-purchase.lua
* shared/main.lua
* web (entire folder)
# v1.2.5 (/dealerships/changelog/v1.2.5)
Sorry, couple of things I only became aware of after I pushed the last update. Last one for this month, promise :)
## Fixes [#fixes]
* /dealeradmin command uses `Framework.Server.IsAdmin` for permissions, rather than being hardcoded
* Fix sell vehicle exploit by disconnecting internet (yes, seriously)
## Changed Files [#changed-files]
* client/cl-sell-vehicle.lua
* server/sv-admin.lua
* web (entire folder)
# v1.2.6 (/dealerships/changelog/v1.2.6)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Fixes [#fixes]
* Store local display vehicles in state bags to reduce likelihood of duplicate display vehicles
* Fix SQL queries causing a `GROUP BY` error on some MariaDB configurations
* Fix `waitFor` timeout error
* Config option for generator removal/radius reduction (to fix traffic disappearing around locations)
* Improve arrow keys accuracy when doing micro adjustments to display vehicle placement
* Option to hide & code opened for vehicle statistics
* Fix potential exploit where plate could be spoofed to sell a cheap vehicle for more money
## Changed Files [#changed-files]
* client/cl-display-vehicles.lua
* client/cl-sell-vehicle.lua
* client/cl-showroom.lua
* config/config.example.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* server/sv-admin.lua
* server/sv-finance.lua
* server/sv-initsql.lua
* server/sv-main.lua
* server/sv-management.lua
* server/sv-sell-vehicle.lua
* shared/main.lua
* web (entire folder)
# v1.2 (/dealerships/changelog/v1.2)
**Now requires ox\_lib to run - [click here](https://github.com/overextended/ox_lib/releases) to download it**
## Features [#features]
* Refreshed codebase, using ox\_lib, cleaner code & better practices
* Performance improves with showroom caching and more efficient database queries
* Preview vehicle images in Showroom (like Advanced Garages)
* Rewritten spawning logic and ability to toggle `CreateVehicleServerSetter`
* Full native Qbox support (with Qbox vehicle importing!) - I love Qbox \<3
* Configure custom vehicle colours (hex) for purchases & display vehicles
* Showroom camera can now be configured with a custom heading
* Prevent shooting in test drives
* Can change chat command names in config
## Integrations [#integrations]
* Integration with Advanced Garages v3 (as a heads up, v3 will *require* this release to work)
* AdvancedParking integration
* okokBanking integration
* MrNewbVehicleKeys & Renewed keys integrations
## Fixes [#fixes]
* Fix issue where vehicles would have a blank props JSON after purchase
* Fix withdraw/deposit NUI exploit
* Fix ability to bypass `ShowroomJobWhitelist` with display vehicles
* Removed escrowed framework-specific code for better compatibility with modified frameworks
* Stopped forcing the plate index to be 0
* Prevented breaking in to display vehicles
* Improved license plate generator
* Lots of small bugs & issues squashed
## Config [#config]
There are a lot of changes to the `config.lua` file including a change to coords. Recommend re-configuring the script from scratch (now inside of the `config` folder). Configurator update coming soon.
## Database [#database]
No database changes!
# v1.3.0 (/dealerships/changelog/v1.3.0)
## Features [#features]
* New & much easier to use vehicle placement system for display vehicles (credits to )
## Bug fixes [#bug-fixes]
* Fix display vehicle duplication when using multi-job scripts (credits to )
* Fix issue where old display vehicle interactions would not delete when refreshing (for job updates etc.)
## Changed Files [#changed-files]
* client/cl-display-vehicles.lua
* config/config.example.lua
* server/sv-finance.lua
* server/sv-management.lua
* web (entire folder)
* locales (translations modified)
# v1.3.1 (/dealerships/changelog/v1.3.1)
## Improvements to new vehicle placer [#improvements-to-new-vehicle-placer]
* Fix bug where vehicle would slowly drift while placing
* Improve vehicle collision zone accuracy
* Vehicle no longer collides with environment at all; making placement easier
## Changed Files [#changed-files]
* client/cl-display-vehicles.lua
# v1.4.0 (/dealerships/changelog/v1.4.0)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Features [#features]
* Ability to add custom payment options (such as VIP coins, crypto, etc.) [Learn more](https://docs.jgscripts.com/dealerships/custom-payment-options)
* Option to disable RGB vehicle colours
* Ability to refresh player balances without exiting purchase UI
* Config opt to allow managers to change sale prices of vehicles
* Configurable pre-check callbacks when purchasing or selling a vehicle
* Added `exports["jg-dealerships"]:generatePlate()` for using our plate gen in other scripts
* Add artifact issues check on start (uses artifacts.jgscripts.com)
* Added more debug prints (enable with `Config.Debug = true`)
## Fixes [#fixes]
* Fix name/commission not always saving in dealership settings
* Prevent showroom being opened while creating display vehicles
## Integrations [#integrations]
* Fuel: Renewed-Fuel, qs-fuelstations, rcore\_fuel
* Banking: Renewed-Banking
* Keys: tgiann-hotwire
## Config [#config]
* Updated `Config.VehicleColourOptions` (required)
* Added `Config.UseRGBColors = true/false` (required)
* Added `Config.ManagerCanChangePriceOfVehicles` (optional)
## Changed Files [#changed-files]
* client/cl-direct-sales.lua
* client/cl-display-vehicles.lua
* client/cl-main.lua
* client/cl-sell-vehicle.lua
* client/cl-showroom.lua
* config/config-sv.lua
* config/config.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* server/sv-admin.lua
* server/sv-direct-sales.lua
* server/sv-employees.lua
* server/sv-finance.lua
* server/sv-locations.lua
* server/sv-main.lua
* server/sv-management.lua
* server/sv-purchase.lua
* server/sv-sell-vehicle.lua
* server/sv-showroom.lua
* server/sv-test-drive.lua
* server/sv-version-check.lua
* shared/main.lua
* fxmanifest.lua
* web (entire folder)
# v1.4.1 (/dealerships/changelog/v1.4.1)
## Fixes [#fixes]
* Fix magical floating showroom vehicles
## Integrations [#integrations]
* tgg-banking
* jacksam's job creator jobs injection (QBCore only)
## Changed Files [#changed-files]
* client/cl-display-vehicles.lua
* framework/qb/cl-qb.lua
* framework/sv-functions.lua
* fxmanifest.lua
# v1.5.0 (/dealerships/changelog/v1.5.0)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
> ?? This contains critical security fixes and you should update as soon as you can
## Security [#security]
* 2 major exploits fixed with server-sided vehicle spawning & vehicle direct sales
* 2 minor exploits fixed with test drives & client-sided vehicle spawning
## Refactoring [#refactoring]
* Rewritten most of the spawning & vehicle purchase code
* Refactored `config-cl.lua` & `config-sv.lua` - *you will need to update any custom code you've added to these!*
## Features [#features]
* Added 2 new finance related events to `config-sv.lua`
* Entity orphan mode
## Fixes [#fixes]
* Changed the duplicate-named `Brazzers-FakePlate` integration events which caused ox\_lib error
## Changed Files [#changed-files]
* client/cl-direct-sales.lua
* client/cl-display-vehicles.lua
* client/cl-locations.lua
* client/cl-main.lua
* client/cl-purchase.lua
* client/cl-sell-vehicle.lua
* client/cl-showroom.lua
* client/cl-spawn.lua
* client/cl-test-drive.lua
* config/config-cl.lua
* config/config-sv.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* server/sv-admin.lua
* server/sv-direct-sales.lua
* server/sv-display-vehicles.lua
* server/sv-employees.lua
* server/sv-finance.lua
* server/sv-initsql.lua
* server/sv-locations.lua
* server/sv-main.lua
* server/sv-management.lua
* server/sv-purchase.lua
* server/sv-sell-vehicle.lua
* server/sv-showroom.lua
* server/sv-spawn.lua
* server/sv-test-drive.lua
* server/sv-version-check.lua
* server/sv-webhooks.lua
* shared/main.lua
* fxmanifest.lua
* web (entire folder)
# v1.5.1 (/dealerships/changelog/v1.5.1)
## Fixes [#fixes]
* Fixed issue where config functions would receive server entity id
* Fix bug where `finishTestDrive()` would spin up multiple threads while running
* Brand is now an optional field in the edit vehicle modal
* Allow model names with uppercase letters to pass checks
## Changed Files [#changed-files]
* client/cl-purchase.lua
* client/cl-test-drive.lua
* server/sv-purchase.lua
* server/sv-sell-vehicle.lua
* server/sv-spawn.lua
* server/sv-test-drive.lua
* fxmanifest.lua
* web (entire folder)
# v1.5.2 (/dealerships/changelog/v1.5.2)
## Fixes [#fixes]
* If a player leaves while the financing cronjob is running; it will crash the job and cause payments to pause until the script is restarted
## Changed Files [#changed-files]
* fxmanifest.lua
# v2.0.0 (/dealerships/changelog/v2.0.0)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
Dealerships v2 - a complete rewrite of the script. See [Migrating from v1](/dealerships/migrating-from-v1) for upgrade instructions and what has changed.
## Changed Files [#changed-files]
* client/cl-admin.lua
* client/cl-coupons.lua
* client/cl-dealership-zones.lua
* client/cl-direct-sales.lua
* client/cl-display-vehicles.lua
* client/cl-employees.lua
* client/cl-finance.lua
* client/cl-instructional-prompt.lua
* client/cl-locations.lua
* client/cl-main.lua
* client/cl-management.lua
* client/cl-purchase.lua
* client/cl-sell-vehicle.lua
* client/cl-showroom.lua
* client/cl-spawn.lua
* client/cl-test-drive.lua
* client/cl-trailer-vehicle-attachment.lua
* client/cl-trucking-mission.lua
* client/interactions/cl-entity-streaming.lua
* client/interactions/cl-interaction-handler.lua
* client/interactions/cl-interactions.lua
* client/interactions/cl-object.lua
* client/interactions/cl-ped.lua
* client/interactions/cl-point.lua
* client/interactions/cl-preview\.lua
* client/interactions/cl-textui-manager.lua
* client/interactions/cl-vehicle.lua
* client/interactions/cl-zone.lua
* config/config-trucking.lua
* config/config.lua
* framework/cl-functions.lua
* framework/esx/cl-esx.lua
* framework/qb/cl-qb.lua
* framework/qbx/cl-qbx.lua
* framework/sv-currencies.lua
* framework/sv-functions.lua
* fxmanifest.lua
* server/sv-admin.lua
* server/sv-commands.lua
* server/sv-coupons.lua
* server/sv-dealership-balance.lua
* server/sv-direct-sales.lua
* server/sv-display-vehicles.lua
* server/sv-employees.lua
* server/sv-finance.lua
* server/sv-import-default-locations.lua
* server/sv-import.lua
* server/sv-locations.lua
* server/sv-main.lua
* server/sv-management.lua
* server/sv-migrate-v2.lua
* server/sv-purchase.lua
* server/sv-sell-vehicle.lua
* server/sv-showroom.lua
* server/sv-spawn.lua
* server/sv-test-drive.lua
* server/sv-trucking-mission.lua
* server/sv-vehicles.lua
* shared/\_types.lua
* shared/main.lua
* shared/search-helpers.lua
* web (entire folder)
# v2.0.1 (/dealerships/changelog/v2.0.1)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## SQL Error Fixed (JSON\_ARRAYAGG) [#sql-error-fixed-json_arrayagg]
I've changed the query to use a MariaDB 10.4 compatible query for fetching admin vehicles, which will enable MariaDB databases via XAMPP to work again. I do want to stress though that I **DO NOT** recommend using XAMPP (it is extremely outdated at this point), and instead you should install MariaDB directly. made a great guide: [https://www.youtube.com/watch?v=RIa3vHxBMvg](https://www.youtube.com/watch?v=RIa3vHxBMvg)
## Locales [#locales]
* Added all missing locales from UI (or as many as I could find!) + translations for them
* You will need to update your `/locales` folder!
## Other Fixes [#other-fixes]
* Fix ESX not loading interactions on script restart
* Importing default locations on a new install caused there to be no colour options by default -> now will be set to RGB selector
* If *Enable Purchase* is disabled, /directsale purchases would error
* If *Enable Test Drives* is disabled, /directsale test drives would error
* Fix `shared/main.lua` error when importing with some new config options missing
* Fix 'color cannot be null' error when running /migratev2
* Use location name if new config opt `Config.BlipNameFormat` is not available
## Changed Files [#changed-files]
* client/cl-locations.lua
* framework/esx/cl-esx.lua
* framework/qb/cl-qb.lua
* framework/sv-currencies.lua
* server/sv-import.lua
* server/sv-migrate-v2.lua
* server/sv-purchase.lua
* server/sv-test-drive.lua
* server/sv-vehicles.lua
* shared/main.lua
* fxmanifest.lua
* web (entire folder)
# v2.1.0 (/dealerships/changelog/v2.1.0)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Bugs [#bugs]
* Display vehicles floating
* Trucking mission will randomly fail for a variety of different reasons
* Dealership embeds showing UUID for dealership instead of name
* Issue where finance payments sometimes don't save & vehicle gets repossessed
* Markers and blips disappearing sometimes after a server restart
* Server admins with a base job still have all permissions within dealership management panel
* Adding cars manually with unlimited stock causes SQL error for some
* Coupons restricted to certain categories not working with vehicles in those selected categories
* Sometimes manual IPL loading can fail; wrap in pcall
* Translations missing for trucking mission prompts
## Security [#security]
* Vehicle price in dealerships admin can be made less than 0 \[low sev]
## Features [#features]
* Add missing open showroom event from v1
## Changed Files [#changed-files]
* client/cl-direct-sales.lua
* client/cl-locations.lua
* client/cl-showroom.lua
* client/cl-trucking-mission.lua
* client/interactions/cl-vehicle.lua
* framework/esx/cl-esx.lua
* framework/qb/cl-qb.lua
* framework/qbx/cl-qbx.lua
* server/sv-employees.lua
* server/sv-finance.lua
* server/sv-management.lua
* server/sv-purchase.lua
* server/sv-test-drive.lua
* server/sv-trucking-mission.lua
* server/sv-vehicles.lua
* shared/\_types.lua
* fxmanifest.lua
* web (entire folder)
# v2.2.0 (/dealerships/changelog/v2.2.0)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
> ?? This update fixes an exploit & we recommend updating as soon as possible.
## Security [#security]
* \[High sev] Unlimited time test drive exploit
## Features [#features]
* Add height adjustment to polyzone creation
* Allow selecting of yourself in direct sale & test drive
* Add some server exports for easier integrations with other scripts or custom functionality
* Allow multiple spawn points for vehicle purchase/test drive/trucking mission
## Bugs [#bugs]
* Vehicles admin UI breaks when there are too many assigned dealerships
* Editing a vehicle value will force you back to page 1 when done with editing
* Keys aren't being removed after trucking mission is completed
* Register society for esx\_society is missing
* Can change interaction type when editing dealership zone
* Coupon codes not updating on new vehicles
* Text UI sometimes gets stuck on the screen
## Logging [#logging]
* Add debug prints to deliveries
## Locales [#locales]
* 3 new locales added; `spawnPoints`, `noSpawnPoints` & `manageSpawnPoints`
## Changed Files [#changed-files]
* client/cl-dealership-zones.lua
* client/cl-direct-sales.lua
* client/cl-display-vehicles.lua
* client/cl-main.lua
* client/cl-purchase.lua
* client/cl-showroom.lua
* client/cl-spawn-preview\.lua
* client/cl-test-drive.lua
* client/cl-trucking-mission.lua
* client/interactions/cl-interaction-handler.lua
* client/interactions/cl-interactions.lua
* client/interactions/cl-preview\.lua
* client/interactions/cl-textui-manager.lua
* client/interactions/cl-vehicle.lua
* client/interactions/cl-zone.lua
* framework/cl-functions.lua
* framework/esx/sv-esx.lua
* server/sv-coupons.lua
* server/sv-dealership-balance.lua
* server/sv-direct-sales.lua
* server/sv-employees.lua
* server/sv-finance.lua
* server/sv-locations.lua
* server/sv-main.lua
* server/sv-purchase.lua
* server/sv-showroom.lua
* server/sv-test-drive.lua
* server/sv-trucking-mission.lua
* server/sv-vehicles.lua
* shared/\_types.lua
* fxmanifest.lua
* web (entire folder)
# v2.2.1 (/dealerships/changelog/v2.2.1)
## Fixes [#fixes]
* Improve reliability of truck and trailer entity & net ID tracking during delivery missions
* Some minor code cleanup
## Changed Files [#changed-files]
* client/cl-spawn.lua
* client/cl-trucking-mission.lua
* client/interactions/cl-vehicle.lua
* server/sv-test-drive.lua
* server/sv-trucking-mission.lua
* fxmanifest.lua
* web (entire folder)
# v2.2.2 (/dealerships/changelog/v2.2.2)
## Integrations [#integrations]
* Add support for JG Vehicle Studio (coming soon)
## Config [#config]
* Added `Config.UseJGVehicleStudio`
* Added `Config.JGVehicleStudioImageSet`
## Changed Files [#changed-files]
* config/config.lua
* server/sv-management.lua
* server/sv-showroom.lua
* shared/main.lua
* fxmanifest.lua
* web (entire folder)
# v2.2.3 (/dealerships/changelog/v2.2.3)
## JG Vehicle Studio Integration Fixes [#jg-vehicle-studio-integration-fixes]
* Fix issues with fallback images
## Changed Files [#changed-files]
* shared/main.lua
* fxmanifest.lua
* web (entire folder)
# v2.3.0 (/dealerships/changelog/v2.3.0)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Features [#features]
* Track separate dealership balances per custom currency
* Unescrow blip code for editing (`client/cl-blips.lua`)
* Migrate to JG UI & design refinement across the script
* Revamped camera in showroom w/ dragcam & interior view
## Integrations [#integrations]
* RX Banking and Notify
* LC-Fuel
* Wasabi Banking
## Bugs [#bugs]
* Direct sale doesn't show dealership custom currencies
* Direct sale doesn't work second time when done twice in a row
* Locations with 'no colour selection' throw SQL error on purchase
* Not able to edit price of cars via dealeradmin or management
* Truck spawn blip showing at incorrect location
* Commission isnt saving to database after updating in-game
## SQL Changes [#sql-changes]
```sql
ALTER TABLE `dealership_locations` ADD COLUMN IF NOT EXISTS `currency_balances` TEXT NULL;
```
## Changed Files [#changed-files]
* client/cl-blips.lua
* client/cl-direct-sales.lua
* client/cl-main.lua
* client/cl-management.lua
* client/cl-showroom.lua
* client/cl-trucking-mission.lua
* client/interactions/cl-interactions.lua
* config/config.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* fxmanifest.lua
* server/sv-admin.lua
* server/sv-dealership-balance.lua
* server/sv-direct-sales.lua
* server/sv-initsql.lua
* server/sv-locations.lua
* server/sv-main.lua
* server/sv-management.lua
* server/sv-migrate-v2.lua
* server/sv-vehicles.lua
* fxmanifest.lua
* web (entire folder)
# v2.3.1 (/dealerships/changelog/v2.3.1)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Bugs [#bugs]
* Dealership admin UIs get stuck on screen
* Admin cannot change an existing dealership vehicle category
* Wasabi banking integration using old exports
* Improved overall script stability & added more logs
## Changed Files [#changed-files]
* client/cl-admin.lua
* client/cl-display-vehicles.lua
* client/cl-main.lua
* client/cl-management.lua
* client/cl-showroom.lua
* client/cl-test-drive.lua
* framework/sv-functions.lua
* server/sv-admin.lua
* server/sv-showroom.lua
* server/sv-test-drive.lua
* server/sv-vehicles.lua
* fxmanifest.lua
* web (entire folder)
# v2.3.2 (/dealerships/changelog/v2.3.2)
## Security [#security]
* Boss menu withdrawals could duplicate funds after a restart
## Bugs [#bugs]
* Show a fallback vehicle colour when no colours are configured
* Vehicle images fall back when Vehicle Studio is not installed
* Cancelling a partially completed delivery order refunded the full amount
* Wasabi society account could cause balance errors
## Changed Files [#changed-files]
* framework/sv-functions.lua
* server/sv-dealership-balance.lua
* server/sv-display-vehicles.lua
* server/sv-management.lua
* server/sv-spawn.lua
* fxmanifest.lua
* web (entire folder)
# Commands (/dealerships/commands-and-events/commands)
| Description | Command | Permission |
| Manage all dealerships and vehicles, including importing data, in-game | /dealeradmin | Admins |
| View financed vehicles, make missed payments, make an early payment or pay in full. If you use Advanced Garages, this is built into the garage. | /myfinance | Everyone |
| Dealership owners and employees can sell vehicles directly to customers in RP without using the showroom | /directsale | Dealership Employees |
# Events (/dealerships/commands-and-events/events)
## Client [#client]
### jg-dealerships:client:open-showroom [#jg-dealershipsclientopen-showroom]
Opens the showroom of the specified dealership.
```lua
-- dealershipId: string
TriggerEvent('jg-dealerships:client:open-showroom', dealershipId)
```
### jg-dealerships:client:open-management [#jg-dealershipsclientopen-management]
Opens the management tab of the specified dealership.
```lua
-- dealershipId: string
TriggerEvent('jg-dealerships:client:open-management', dealershipId)
```
# All Integrations (/dealerships/integrations)
## Key [#key]
## Garages [#garages]
Most other basic garage systems using the standard QB/ESX player-owned vehicles database table should work fine.
## Society banking [#society-banking]
Using Society Purchase? Read our [society banking setup guide](/dealerships/integrations/society-banking) to register each account or connect another banking resource.
## Insurance/Documents/Contracts [#insurancedocumentscontracts]
## Fuel [#fuel]
| Script | Status | Integrating |
| LegacyFuel | ✅ Officially Supported | Config.FuelSystem = "LegacyFuel" |
| ti_fuel | ✅ Officially Supported | Config.FuelSystem = "ti_fuel" |
| ps-fuel | ☑️ Supported | Config.FuelSystem = "ps-fuel" |
| lj-fuel | ☑️ Supported | Config.FuelSystem = "lj-fuel" |
| ox_fuel | ☑️ Supported | Config.FuelSystem = "ox_fuel" |
| cdn-fuel | ☑️ Supported | Config.FuelSystem = "cdn-fuel" |
| hyon_gas_station | ☑️ Supported | Config.FuelSystem = "hyon_gas_station" |
| okokGasStation | ☑️ Supported | Config.FuelSystem = "okokGasStation" |
| nd_fuel | ☑️ Supported | Config.FuelSystem = "nd_fuel" |
| myFuel | ☑️ Supported | Config.FuelSystem = "myFuel" |
| Renewed Fuel | ☑️ Supported | Config.FuelSystem = "Renewed-Fuel" |
| RCore Fuel | ☑️ Supported | Config.FuelSystem = "rcore_fuel" |
| qs-fuelstations | ☑️ Supported | Config.FuelSystem = "qs-fuelstations" |
| lc_fuel | ☑️ Supported | Config.FuelSystem = "lc_fuel" |
Using an unsupported fuel system? Read our guide on [custom fuel systems](/advanced-garages/integrations/custom-fuel-system).
## Key Systems [#key-systems]
| Script | Status | Integrating |
| qb-vehiclekeys | ✅ Officially Supported | Config.VehicleKeys = "qb-vehiclekeys" |
| jaksam-vehicles-keys | ☑️ Supported | Config.VehicleKeys = "jacksam-vehicles-keys" |
| mk_vehiclekeys | ☑️ Supported | Config.VehicleKeys = "mk_vehiclekeys" |
| wasabi_carlock | ☑️ Supported | Config.VehicleKeys = "wasabi_carlock" |
| cd_garage | ☑️ Supported | Config.VehicleKeys = "cd_garage" |
| okokGarage | ☑️ Supported | Config.VehicleKeys = "okokGarage" |
| t1ger_keys | ☑️ Supported | Config.VehicleKeys = "t1ger_keys" |
| qs-vehiclekeys | ☑️ Supported | Config.VehicleKeys = "qs-vehiclekeys" |
| MrNewbVehicleKeys | ☑️ Supported | Config.VehicleKeys = "MrNewbVehicleKeys" |
| tgiann-hotwire | ☑️ Supported | Config.VehicleKeys = "tgiann-hotwire" |
| Renewed Vehicle Keys | ☑️ Supported | Config.VehicleKeys = "Renewed" |
Using an unsupported key system? Read our guide on [custom key systems](/advanced-garages/integrations/custom-key-system).
## Notifications [#notifications]
| Script | Status | Integrating |
| Built-in | ✅ Officially Supported | Config.Notifications = "default" |
| RxNotify | ☑️ Supported | Config.Notifications = "RxNotify" |
| okokNotify | ☑️ Supported | Config.Notifications = "okokNotify" |
| ox_lib | ☑️ Supported | Config.Notifications = "ox_lib" |
| ps-ui | ☑️ Supported | Config.Notifications = "ps-ui" |
## Text UI [#text-ui]
| Script | Status | Integrating |
| jg-textui | ✅ Officially Supported | Config.DrawText = "jg-textui" |
| qb-DrawText | ✅ Officially Supported | Config.DrawText = "qb-DrawText" |
| okokTextUI | ✅ Officially Supported | Config.DrawText = "okokTextUI" |
| ps-ui | ✅ Officially Supported | Config.DrawText = "ps-ui" |
| ox_lib | ☑️ Supported | Config.DrawText = "ox_lib" |
## 3D Text UI [#3d-text-ui]
| Script | Status | Integrating |
| sleepless_interact | ☑️ Supported | Config.DrawText3d = "sleepless_interact" |
# okokContract (/dealerships/integrations/okokcontract)
Change the table name depending on your framework: `owned_vehicles` for ESX, or `player_vehicles` for QBCore.
The function below can be found in the okokContract resource.
```lua
function canVehicleBeSold(source, plate)
local canBeSold = true
local financed = MySQL.scalar.await('SELECT `financed` FROM `owned_vehicles` WHERE `plate` = ? LIMIT 1', { plate })
if financed then
TriggerClientEvent('okokNotify:Alert', source, "NO TRANSFER", "You cannot transfer a vehicle that is financed", 5000, 'error')
elseif not financed then
return canBeSold
end
end
```
# Pickle Mods Documents (/dealerships/integrations/pickle-mods-documents)
Registers your vehicle after purchase.
Buy it here: [https://store.picklemods.com/package/5447777](https://store.picklemods.com/package/5447777)
## Replace the jg-dealerships:client:purchase-vehicle:config event located in the config-cl.lua with this below [#replace-the--jg-dealershipsclientpurchase-vehicleconfig-event-located-in-the-config-cllua-with-this-below]
```lua
RegisterNetEvent("jg-dealerships:client:purchase-vehicle:config", function(vehicle, plate, purchaseType, amount, paymentMethod, financed)
TriggerServerEvent("jg-dealerships:server:AddVehicleRegistration", plate)
end)
```
## Add event below into config-sv.lua [#add-event-below-into-config-svlua]
```lua
RegisterNetEvent("jg-dealerships:server:AddVehicleRegistration", function(plate)
local src = source
local matches = MySQL.query.await("SELECT * FROM `dealership_sales` WHERE `plate` = :plate LIMIT 1", {
plate = plate,
})
local data = matches[1]
dealer = data['dealership']
exports.pickle_documents:GiveDocument(src, "VehicleRegistration", {
plate = plate,
dealer = dealer,
}, { takePhoto = true })
end)
```
# qb-vehiclesales (/dealerships/integrations/qb-vehiclesales)
## Go to file `qb-vehiclesales/server/main.lua` [#go-to-file-qb-vehiclesalesservermainlua]
Add this snippet at the bottom of the script.
```lua
QBCore.Functions.CreateCallback("jg-advancedgarages:server:checkVehicleOwner", function(source, cb, plate)
local src = source
local pData = QBCore.Functions.GetPlayer(src)
local financed = MySQL.scalar.await('SELECT `financed` FROM `player_vehicles` WHERE `plate` = ? LIMIT 1', { plate })
if not financed then
MySQL.query('SELECT * FROM player_vehicles WHERE plate = ? AND citizenid = ?',{plate, pData.PlayerData.citizenid}, function(result)
if result[1] then
cb(true, result[1].balance)
else
cb(false)
end
end)
elseif financed then
TriggerClientEvent('QBCore:Notify', src, "you cannot sell a vehicle that is under finance", 'error')
end
end)
```
## Go to file `qb-vehiclesales/client/main.lua` [#go-to-file-qb-vehiclesalesclientmainlua]
Replace `qb-garage:server:checkVehicleOwner` with `jg-advancedgarages:server:checkVehicleOwner` (there are 2 occurrences to change - use find and replace in your text editor), like in the screenshot below:
# qbx_vehiclesales (/dealerships/integrations/qbx_vehiclesales)
## Go to file `qbx_vehiclesales/server/main.lua` [#go-to-file-qbx_vehiclesalesservermainlua]
Replace lines 38-48 with the code block below.
```lua
lib.callback.register('jg-advancedgarages:server:checkVehicleOwner', function(source, plate)
local player = exports.qbx_core:GetPlayer(source)
local result = MySQL.single.await('SELECT * FROM player_vehicles WHERE plate = ? AND citizenid = ?', {plate, player.PlayerData.citizenid})
if result and result.id then
local financeRow = MySQL.single.await('SELECT * FROM vehicle_financing WHERE vehicleId = ?', {result.id})
if financeRow and financeRow.balance > 0 then
exports.qbx_core:Notify(source, "You cannot sell a vehicle that is under finance", 'error')
return false
end
return true, financeRow?.balance or 0
end
return false
end)
```
## Go to file `qbx_vehiclesales/client/main.lua` [#go-to-file-qbx_vehiclesalesclientmainlua]
Replace `qbx_vehiclesales:server:checkVehicleOwner` with `jg-advancedgarages:server:checkVehicleOwner` (there are 2 occurrences to change - use find and replace in your text editor), like in the screenshot below:

# Society Banking (/dealerships/integrations/society-banking)
`Config.UseFrameworkJobs = true` is enabled by default. With it enabled, JG Dealerships uses each business's internal job name as its society account name. A business with the job `cardealer` needs a society account named `cardealer`.
Society Purchase also uses the internal job or gang name from its whitelist as the account name.
Creating a dealership business or adding a job or gang to a Society Purchase whitelist doesn't create its bank account. A missing or mismatched account can make the business balance show `0` and prevent deposits, withdrawals or society purchases.
Start your banking resource before `jg-dealerships` in `server.cfg`.
## okokBanking [#okokbanking]
1. Open `okokBanking/config.lua`.
2. Find `Config.Societies`.
3. Add each internal job name used by a dealership business or Society Purchase whitelist. Keep your existing entries.
For current okokBanking versions, add the job and the grades that can manage its account:
```lua title="okokBanking/config.lua"
["cardealer"] = {0, 1, 2, 3, 4},
```
Replace `cardealer` with the exact internal job name. Older okokBanking versions may list society names without grades. Use the same format as the other entries in your installed version.
See the [okokBanking configuration reference](https://docs.okokscripts.io/scripts/okokbankingv2/config-file).
## TGG Banking [#tgg-banking]
1. Give your account admin permission in TGG Banking.
2. Open the banking interface and select **Admin Actions**.
3. Create a society account.
4. Set **Society Name** to the exact internal job or gang name, such as `cardealer`. **Display Name** can be any player-facing name.
Repeat this for every business job and every job or gang used in a Society Purchase whitelist.
See the [TGG Banking society account setup](https://www.teamsgg.dev/docs/paid-scripts/banking/installation#society-accounts).
## Connect another banking resource [#connect-another-banking-resource]
Your banking resource needs server exports that can read, remove and add money for a society account.
Open `jg-dealerships/framework/sv-functions.lua`. In each function below, add an `elseif` block after the built-in banking blocks and before the `QBCore` block.
The resource and export names below are examples. Replace them with the names from your banking resource's server export documentation.
### GetSocietyBalance [#getsocietybalance]
This export must return the account balance as a number.
```lua title="jg-dealerships/framework/sv-functions.lua"
elseif GetResourceState("your-banking-resource") == "started" then
return exports["your-banking-resource"]:GetSocietyBalance(society)
```
### RemoveFromSocietyFund [#removefromsocietyfund]
```lua title="jg-dealerships/framework/sv-functions.lua"
elseif GetResourceState("your-banking-resource") == "started" then
exports["your-banking-resource"]:RemoveSocietyMoney(societyName, amount)
```
### AddToSocietyFund [#addtosocietyfund]
```lua title="jg-dealerships/framework/sv-functions.lua"
elseif GetResourceState("your-banking-resource") == "started" then
exports["your-banking-resource"]:AddSocietyMoney(societyName, amount)
```
Fully restart your server. Open the management interface for a framework-job business and check that the correct balance appears. Deposit and withdraw a small amount, then confirm that the same society account changes.
If you're using Society Purchase, open the showroom and check that the correct balance appears. Complete a test purchase and confirm that the same account is charged.
## Troubleshooting [#troubleshooting]
Check that the account exists in your banking resource. Its name must match the business job or Society Purchase whitelist entry exactly. Names are case-sensitive. Restart the banking resource before restarting `jg-dealerships`.
Check that `Framework.Server.AddToSocietyFund` calls the correct server export for your banking resource. Confirm that the export accepts the society name first and the amount second.
Check that `Framework.Server.RemoveFromSocietyFund` calls the correct server export for your banking resource. Confirm that the export accepts the society name first and the amount second.
# Changelog (/handling/changelog)
# v1.0.0 (/handling/changelog/v1.0.0)
Initial release.
# v1.1.0 (/handling/changelog/v1.1.0)
## Features [#features]
* Added new apply option - apply to all vehicles with this model
* Improved UX for apply handling modal
* Option to change keybind for resetting timing tool
## Fixes [#fixes]
* Fix issue where plate saved handling would be NULL in database
* Fix issue where JG Mechanic export Lua code wouldn't change based on Overwrite/Modify
* Other little bits of cleanup and optimisation
## QoL [#qol]
* Added update & artifacts checker
## Translations [#translations]
* There have been some significant updates so make sure you replace your locales folder or some of the updated UI won't be clear!
## New Config Options [#new-config-options]
```lua
Config.TimingToolResetKeyBind = 36
Config.TimingToolResetLabel = "CTRL"
```
## Changed Files [#changed-files]
* client/cl-editor.lua
* client/cl-telemetry.lua
* client/cl-vehicle.lua
* server/sv-main.lua
* server/sv-profiles.lua
* server/sv-vehicle.lua
* server/sv-version-check.lua
* web (entire folder)
* config (config files changed)
# v1.2.0 (/handling/changelog/v1.2.0)
**What's New?**
* Refined new design for the editing tablet
* Redesigned timing tool with a more modern appearance
* Search in visual flags editor
## Changed Files [#changed-files]
* client/cl-editor.lua
* client/cl-main.lua
* client/cl-telemetry.lua
* client/cl-vehicle.lua
* server/sv-initsql.lua
* server/sv-main.lua
* server/sv-profiles.lua
* server/sv-vehicle.lua
* shared/main.lua
* web (entire folder)
# Changelog (/hud/changelog)
# v1.0.1 (/hud/changelog/v1.0.1)
## Fixes [#fixes]
* Show default GTA V HUD components such as 'help text' notifications
* Config.EnableCruiseControl now actually disables cruise control
* Fixed minimap disappearing/going grey (add `Config.UpdateRadarZoom = true` to your config)
* Fixed issue where minimap was sometimes off-center on 21:9 ultrawides
* Improved handling of NUI postMessages
* Fixed QBCore hunger & thirst not displaying until they decrease
* Add ability to customise displayed currency
* Hide ped avatar by default
* Toggle engine has been added to cl-functions (`Framework.Client.ToggleEngine`) to allow for key integrations
## New Config Options [#new-config-options]
*Make sure these new config options are added to your config.lua!*
* `Config.UpdateRadarZoom = true`
* `Config.Currency = "$"`
* `Config.NumberFormat = "en-US"`
**If you're having issues with HUD not loading in, try disabling the pedAvatar:**
```lua
Config.ShowComponents = {
pedAvatar = false,
...
```
## Changed Files [#changed-files]
* client/cl-cruise-control.lua
* client/cl-main.lua
* client/cl-radar.lua
* client/cl-vehicle-controls.lua
* framework/cl-functions.lua
* web (entire folder)
* config (config files changed)
# v1.0 (/hud/changelog/v1.0)
Initial release.
# v1.1.1 (/hud/changelog/v1.1.1)
## Hotfixes [#hotfixes]
* Stop HUD from loading in before player is ready on QBCore
* Changed statebag from `isLoggedIn` to `jgHudPlayerLoggedIn` & added login bridge for Qbox
* Added additional login event listener for ESX (`esx:onPlayerSpawn`)
## Changed Files [#changed-files]
* client/cl-main.lua
* framework/cl-functions.lua
# v1.1.2 (/hud/changelog/v1.1.2)
## Improvements & Fixes [#improvements--fixes]
* Fix HUD init/NUI ready race condition (I think this is the cause of the HUD not appearing for some players!!)
* Fix issue where status percentages didn't animate
* Added some debug prints
* Added config option to change KVP prefix (allows you to essentially clear everyone's existing settings if you want to set a new default-settings.json)
* Fix issue where stress would disappear when going over 100, or is exactly 0
* Fix issue where minimap would display in the wrong location when settings/layout editing is disabled in the config
## New Config Options [#new-config-options]
* `Config.Debug = false`
* `Config.DefaultSettingsKvpPrefix = "hud-"`
## Changed Files [#changed-files]
* client/cl-main.lua
* client/cl-ped.lua
* client/cl-settings.lua
* web (entire folder)
* config (config files changed)
# v1.1.3-hotfix (/hud/changelog/v1.1.3-hotfix)
## Hotfix [#hotfix]
* Fixes attempt to compare nil with number on QB/ESX (my apologies!)
## Changed Files [#changed-files]
* framework/cl-functions.lua
# v1.1.3 (/hud/changelog/v1.1.3)
## Fixes [#fixes]
* Fix minimap having strange border/spacing when in an IPL (such as Cayo)
* Fix minimap being very very slightly too wide (just me? ok)
* Fix issue where compass cardinal direction was flipped when following camera
* Fix seatbelt state not being reset when exiting vehicle
* Fix "turn off compass on foot" menu option not working for minimal small & advanced styles
* Display area name as street name when street name is not available (looks prettier)
* Display infinity symbol when weapon ammo is unlimited
* Add some missing weapon names & images from GTA DLCs
* Fix issue where square status symbols would display 90% when at 100%
* Fix "RangeError values need to be sorted" bug
* Prevent indicators from being used from other seats in the vehicle
## Integrations [#integrations]
* Show mileage from t1ger\_mechanic
* Show gang if using rcore\_gangs
## New Config Option [#new-config-option]
* Force disabling of compass when on foot: `Config.ShowCompassOnFoot = true/false`
## JG Vehicle Mileage v2.0.1 [#jg-vehicle-mileage-v201]
* There has also been a small update to fix a bug with jg-vehiclemileage. You can download it here (if you're using it): [https://github.com/jgscripts/jg-vehiclemileage/releases/tag/v2.0.1](https://github.com/jgscripts/jg-vehiclemileage/releases/tag/v2.0.1)
## Changed Files [#changed-files]
* client/cl-indicators.lua
* client/cl-main.lua
* client/cl-ped.lua
* client/cl-radar.lua
* client/cl-seatbelt.lua
* client/cl-vehicle.lua
* framework/cl-functions.lua
* web (entire folder)
* config (config files changed)
# v1.1 (/hud/changelog/v1.1)
## New Features [#new-features]
* Added dirty money
* Added in-game options to disable compass and/or minimap when on foot
**The issue with HUD not loading for some players (mainly ESX)**
> A quick note from me on this one - I still haven't been able to reproduce it myself. I've made some solid improvements, however, to the login bridge; such as removing duplicate events, conducting more checks and adding some more `Wait`s where necessary. I'm hoping this will resolve any remaining issues here. If not, let me know.
## Fixes [#fixes]
* Fix attempt to call a nil value/syntax error on Canary clients due to today's Lua 5.4 update
* Fix large map showing instead of standard minimap
* Fix "Toggle cruise control" being displaying instead of "Toggle seatbelt"
* Prevent cruise control from being used when vehicle is off
* Fix issue where HUD would crash if vehicle is deleted/exited while vehicle control is open
* Fix issue where if settings is opened when vehicle control is open, NUI focus will be glitchy
* Fix issue where advanced & minimal small compasses didn't move down when minimap is hidden
* Fix issue where player health would show as double the player's actual health (would die at 50%)
* Fix the laggy scrolling in the "Player Health" settings page
* Fix visual bug where if translated sidebar text was too long it was cut off
## Translations [#translations]
* 3 new translations have been added to `locales/en.lua`
## Config [#config]
* Added `Config.ShowComponents.dirtyMoneyBalance` (defaulted to false)
## Changed Files [#changed-files]
* client/cl-cruise-control.lua
* client/cl-main.lua
* client/cl-nearest-postal.lua
* client/cl-ped.lua
* client/cl-radar.lua
* client/cl-seatbelt.lua
* client/cl-settings.lua
* client/cl-vehicle-controls.lua
* client/cl-vehicle.lua
* framework/cl-functions.lua
* fxmanifest.lua
* web (entire folder)
* config (config files changed)
# v1.2 (/hud/changelog/v1.2)
## Features [#features]
* Ability to set custom names for streets and zones [learn more](https://docs.jgscripts.com/hud/custom-street-zone-names)
* Integrate third-party seatbelt scripts (+ `cl-seatbelt.lua` has been unencrypted) [learn more](https://docs.jgscripts.com/hud/integrations/third-party-seatbelts)
* Advanced compass expands with container width
* Choose between local (IRL) time and server time (/settings -> Player Info tab)
## Fixes [#fixes]
* Improved performance
* Fix F6 exploit where turning on vehicle headlights started the vehicle
* Increase NUI init wait time so it doesn't error out after waiting 30 seconds
* Fixed RangeError/values need to be sorted NUI error
* Fix issue where conflicting resources would cause minimap to show on foot (when disabled)
* Gang updates on change if using rcore\_gangs
* Fix issue where `seatbelt` statebag wasn't always synced
* \[UI] Aircraft horizon indicator is now always centered & animates more smoothly
* \[UI] Improvements to the "Modern Pro" redline gradient
* \[UI] Fix issue where UK/EU speed limit sign would not be square
## New Config Opts [#new-config-opts]
* `Config.UseCustomSeatbeltIntegration = true/false` (false by default to use built-in system)
* Some new stuff added to `config.data.lua`, but script still works fine if omitted/you don't plan the use the renaming features
## New Translations [#new-translations]
* `serverTime = "Server time"`
* `localTime = "Local time"`
## Changed Files [#changed-files]
* client/cl-main.lua
* client/cl-nearest-postal.lua
* client/cl-ped.lua
* client/cl-radar.lua
* client/cl-seatbelt.lua
* client/cl-settings.lua
* client/cl-vehicle-controls.lua
* client/cl-vehicle.lua
* framework/cl-functions.lua
* fxmanifest.lua
* server/sv-main.lua
* stream/minimap.gfx
* web (entire folder)
* config (config files changed)
# v1.3-hotfix (/hud/changelog/v1.3-hotfix)
Sorrryyyy I made a little mistake in a file :( a full day of testing and I still screw things up
# v1.3.1 (/hud/changelog/v1.3.1)
## Fixes [#fixes]
* Fix stamina not showing when swimming
* Improved integration with rcore\_gangs
* Slight tweak to seatbelts
* Fix weapon ammo not always updating on reload
## Changed Files [#changed-files]
* client/cl-ped.lua
* client/cl-seatbelt.lua
* client/cl-weapons.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
# v1.3.2 (/hud/changelog/v1.3.2)
## Fixes [#fixes]
* Fix the restreamed minimap.gfx file so it shows the navigation overlay
## Bonus [#bonus]
I've added a guide on my blog to explain how we edit this file from the base game, in case this helps anyone: [https://blog.jgscripts.com/hiding-health-armour-bars-minimap-gfx](https://blog.jgscripts.com/hiding-health-armour-bars-minimap-gfx)
## Changed Files [#changed-files]
* stream/minimap.gfx
# v1.3 (/hud/changelog/v1.3)
## Improvements [#improvements]
* Some styling changes to the settings panel
* Improved performance, code cleanup & various UI fixes
* Minimap movement and resizing now compatible with the "Fixed Sized NUI" option!
## Fixes [#fixes]
* Mileage now uses distance measurement selection rather than speed measurement
* Fix issue where minimap would display when using toggle export or /togglehud in-game
* Updated minimap.gfx scaleform for build 3570 (thanks to )
## Integrations [#integrations]
* jim-mechanic seatbelt & mileage (thanks to )
## Changed Files [#changed-files]
* client/cl-main.lua
* client/cl-nearest-postal.lua
* client/cl-ped.lua
* client/cl-radar.lua
* client/cl-settings.lua
* framework/cl-functions.lua
* fxmanifest.lua
* stream/minimap.gfx
* web (entire folder)
# v1.4.0 (/hud/changelog/v1.4.0)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Features [#features]
* Auto-cancel indicators
* Ability to add indicators to vehicles without them such as trailers (`Config.AddIndicatorCoronas`)
## Bugs [#bugs]
* Stress pulsing when it is 0%
* Indicators don't work on attached trailers
* Using cruise control with nitrous maintains increased speed
* Don't allow cuffed players to use vehicle controls (switch seat, etc)
## Integrations [#integrations]
* Integrate gear & RPM with LegacyDMC's dynamic script
## Refactoring [#refactoring]
* Refactored codebase to improve stability
## Changed Files [#changed-files]
* client/cl-cruise-control.lua
* client/cl-indicators.lua
* client/cl-main.lua
* client/cl-nearest-postal.lua
* client/cl-ped.lua
* client/cl-radar.lua
* client/cl-seatbelt.lua
* client/cl-settings.lua
* client/cl-trains.lua
* client/cl-vehicle-controls.lua
* client/cl-vehicle.lua
* client/cl-weapons.lua
* framework/cl-functions.lua
* framework/main.lua
* server/sv-main.lua
* server/sv-version-check.lua
* web (entire folder)
* config (config files changed)
# v1.4.1 (/hud/changelog/v1.4.1)
## Features [#features]
* Move health & vehicle telemetry to user-configurable functions in `cl-functions.lua`
* Config option for blacklisting specific models from the vehicle HUD
* Support for electric vehicles config on newer game builds
## Integrations [#integrations]
* Add wasabi\_ambulance compatibility for isDead
* Support C.H.A.S.E.R for vehicle telemetry
## Config [#config]
* Added `Config.BlacklistedVehicleHudModels` to `config.data.lua`
## Changed Files [#changed-files]
* client/cl-main.lua
* client/cl-ped.lua
* client/cl-vehicle.lua
* framework/cl-functions.lua
* config (config files changed)
# Fuel Systems (/hud/integrations/fuel-systems)
The fuel gauge reads your vehicle's fuel level from your fuel script. Set `Config.FuelSystem` in `config.lua` to the script you use:
```lua
Config.FuelSystem = "ox_fuel"
```
## Supported fuel scripts [#supported-fuel-scripts]
| Script | `Config.FuelSystem` value |
| ------------------------------------------------------------------------------ | ------------------------- |
| [LegacyFuel](https://github.com/InZidiuZ/LegacyFuel) | `LegacyFuel` |
| lc\_fuel | `lc_fuel` |
| [ps-fuel](https://github.com/Project-Sloth/ps-fuel) | `ps-fuel` |
| [lj-fuel](https://github.com/liberium12/lj-fuel) | `lj-fuel` |
| [ox\_fuel](https://github.com/overextended/ox_fuel) | `ox_fuel` |
| [cdn-fuel](https://github.com/CodineDev/cdn-fuel) | `cdn-fuel` |
| [hyon\_gas\_station](https://github.com/HyonScript/hyon_gas_station) | `hyon_gas_station` |
| okokGasStation | `okokGasStation` |
| nd\_fuel | `nd_fuel` |
| myFuel | `myFuel` |
| [ti\_fuel](https://tebit.tebex.io/package/5266389) | `ti_fuel` |
| [Renewed-Fuel](https://forum.cfx.re/t/renewed-fuel-esx-qbcore-ox-qbox/5187220) | `Renewed-Fuel` |
| [rcore\_fuel](https://store.rcore.cz/package/6222210) | `rcore_fuel` |
If you don't use a fuel script, set it to `none` and the gauge will always show full. You can also wire up a custom fuel system in `framework/cl-functions.lua`.
# Third-Party Seatbelts (/hud/integrations/third-party-seatbelts)
The seatbelt key bind, button in the F6 vehicle control menu, sound effects, and indicator light within JG HUD will still work when using a third-party seatbelt integration!
1. Within `config.lua`, set `Config.UseCustomSeatbeltIntegration = true`. Ensure that `Config.EnableSeatbelt = true`, or it won't work.
2. Head to `framework/cl-functions.lua` and find `Framework.Client.ToggleSeatbelt`. There are two parameters, `vehicle` & `seatbeltOn`. `seatbeltOn` will be true if the seatbelt has been enabled, otherwise false. If you have multiple exports, one for enabling the seatbelt and one for disabling, you'll need to use `seatbeltOn` in an if statement; for example:
```lua
if seatbeltOn then
exports["my_seatbelt_script"]:enableSeatbelt()
else
exports["my_seatbelt_script"]:disableSeatbelt()
end
```
# Commands (/mechanic/commands-events-and-exports/commands)
| Description | Command | Permissions |
| Open Tablet | /tablet | Mechanics |
| Manage Mechanics | /mechanicadmin | Admins |
| Fixes the current vehicle | /vfix | Admins |
# Events (/mechanic/commands-events-and-exports/events)
## Client [#client]
### Open tuning menu [#open-tuning-menu]
Opens the tuning menu at a specific location.
```lua
-- mechanicId: string (The mechanic id from the config)
-- mechanicLabel: string (The label which will show in the menu)
TriggerEvent("jg-mechanic:client:open-customisation-menu", mechanicId, mechanicLabel)
```
#### Example Usage [#example-usage]
```lua
ReigsterCommand('openmechmenu', function() -- This creates a command called openmechmenu (maybe for admins?)
TriggerEvent("jg-mechanic:client:open-customisation-menu", 'bennys', 'bennys') -- Trigger Actual event
end, true) -- Is it locked? (https://natives.avarian.dev/?native=0x5FA79B0F&game=gta5)
```
### Open tablet [#open-tablet]
Opens the tablet.
```lua
TriggerEvent("jg-mechanic:client:use-tablet")
```
#### Example usage [#example-usage-1]
```lua
exports.ox_target:addBoxZone({
coords = vector3(0.0, 0.0, 0.0), -- Vector of where you wanted the box zone
size = vec3(1.0, 1.0, 2.0),
rotation = 0,
debug = false,
options = {
{
label = "Use Tablet",
icon = "fa-solid fa-tablet-screen-button",
onSelect = function()
TriggerEvent("jg-mechanic:client:use-tablet")
end,
}
}
})
```
### Open admin menu [#open-admin-menu]
Opens the admin menu.
```lua
TriggerEvent("jg-mechanic:client:open-admin")
```
#### Example Usage [#example-usage-2]
```lua
exports.ox_target:addBoxZone({
coords = vector3(0.0, 0.0, 0.0), -- Vector of where you wanted the box zone
size = vec3(1.0, 1.0, 2.0),
rotation = 0,
debug = false,
options = {
{
label = "Open Admin",
icon = "fa-solid fa-tablet-screen-button",
onSelect = function()
TriggerEvent("jg-mechanic:client:open-admin")
end,
}
}
})
```
### Listeners [#listeners]
#### Listen to toggling of tablet being hidden [#listen-to-toggling-of-tablet-being-hidden]
```lua
AddEventHandler("jg-mechanic:client:tablet-hidden-for-interaction", function()
end)
```
#### Example usage [#example-usage-3]
```lua
AddEventHandler("jg-mechanic:client:tablet-hidden-for-interaction", function()
-- Example: Print something random when tablet is hidden
print('Scorpion is the best support member')
end)
```
#### Listen to toggling of tablet being shown [#listen-to-toggling-of-tablet-being-shown]
```lua
AddEventHandler("jg-mechanic:client:tablet-shown-after-interaction", function()
end)
```
```lua
AddEventHandler("jg-mechanic:client:tablet-shown-after-interaction", function()
print('James is cool')
end)
```
# Exports (/mechanic/commands-events-and-exports/exports)
## Server Exports [#server-exports]
### vehiclePlateUpdated [#vehicleplateupdated]
Tells JG Mechanic a vehicle's plate has changed. Run this export in all scripts that update plates, otherwise you'll lose your vehicle's data.
```lua
-- server only
-- plate: string - the current plate
-- newPlate: string - the new plate
exports["jg-mechanic"]:vehiclePlateUpdated(plate, newPlate)
```
### doesVehicleNeedServicing [#doesvehicleneedservicing]
Returns whether a vehicle needs servicing.
```lua
-- server only
-- plate: string
local needsService = exports["jg-mechanic"]:doesVehicleNeedServicing(plate)
```
| Returns | Type | Description |
| -------------- | --------- | ----------------------------------- |
| `needsService` | `boolean` | Whether the vehicle needs servicing |
### getVehicleServiceHistory [#getvehicleservicehistory]
Returns the service history for a specific vehicle.
```lua
-- server only
-- plate: string
local history = exports["jg-mechanic"]:getVehicleServiceHistory(plate)
--[[ example result:
{
{
id = 1,
date = 1710000000,
identifier = "license:...",
plate = "ABC 123",
mechanic_label = "LS Customs",
serviced_paort = "engine_oil",
mechanic = "license:...",
mileage_km = 4200,
},
...
}
]]
```
| Returns | Type | Description |
| --------- | --------- | ------------------------------------------------------------------- |
| `history` | `table[]` | One record per service, newest first - see the example result shape |
# Changelog (/mechanic/changelog)
# v1.0.1 (/mechanic/changelog/v1.0.1)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Fixes [#fixes]
* Added all missing locales (mostly management & employees)
* %n error with locales text replacement
* Turbo not sticking in tablet
* Money from tuning orders not going into society fund
* UI crash when using dyno share sheet, sending an invoice or using lighting controller
* "Stop vehicle first" should happen less often or when the vehicle isn't actually moving
* Prevent passenger from using tuning menu
* Invisibility exploit for passengers when using tuning menu
* `vehShared` error on Qbox
* The massive number of errors when opening tuning app (you were missing the `tuning = {}` in your config, but it will no longer crash if this is missing)
* Disabling tuning options now *actually* disables them
* Vehicle lifts being in the wrong direction (hopefully fixed now)
## Config [#config]
No config changes! I just changed some defaults...
## Locales [#locales]
Some new locales were added that were previously missing! If you've already made a locale please diff check if that's OK!
## Changed Files [#changed-files]
* client/cl-carlift.lua
* client/cl-dyno.lua
* client/cl-lightcontroller.lua
* client/cl-minigames.lua
* client/cl-mods.lua
* client/cl-tablet.lua
* client/cl-tuning.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* server/sv-carlift.lua
* server/sv-invoice.lua
* server/sv-main.lua
* server/sv-mods.lua
* web (entire folder)
* locales (translations modified)
# v1.0.10 (/mechanic/changelog/v1.0.10)
## Changes [#changes]
* Integration with Garages v3 (coming soon)
* Remove some framework-specific SQL queries causing errors + issues with people using modified frameworks
* Fix issue where $0 invoices could not be deleted
* Other minor under the hood boring things
**New features will be coming! Have a lot of other urgent work that I have been doing on the Garages first, please bear with me!**
## Changed Files [#changed-files]
* client/cl-nitrous.lua
* client/cl-servicing.lua
* client/cl-vehicleprops.lua
* framework/sv-functions.lua
* server/sv-fixing.lua
* server/sv-invoice.lua
* server/sv-management.lua
* server/sv-orders.lua
* shared/main.lua
# v1.0.11 (/mechanic/changelog/v1.0.11)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## PLEASE READ BEFORE UPDATING [#please-read-before-updating]
1. There is a new database table you need to add:
```sql
CREATE TABLE IF NOT EXISTS `mechanic_vehicledata` (
`plate` varchar(10) NOT NULL PRIMARY KEY,
`data` text NOT NULL
);
```
2. You are no longer *required* to use the jg-mechanic get/setVehicleProperties exports. You can use your own. I guess ours is recommended, but I would also recommend ox\_lib's.
3. If another script changes your vehicle's plate, you *NEED* to run this export (server-side) or you will lose your vehicle's data:
```lua
exports["jg-mechanic"]:vehiclePlateUpdated(currentPlate, newPlate)
```
## The boring stuff (Fixes) [#the-boring-stuff-fixes]
* Rewritten the storage of custom vehicle properties to a new data layer separate from the vehicle props JSON.
* Fix scenario where vehicle tuning, servicing, stancing would disappear in de-sync scenarios.
* Fix free upgrades exploit (due to previous preview data cache not being cleared)
* Unencrypted vehicle repair code
* Fix where wheels would be installed immediately when placing an order for them
* Switch xenons to colour IDs so they sync
* Add vehicle blacklist for servicing
* Add vehicle & engine type blacklist for config.tuning parts
* Add audio streaming timeout
* Fix repeatable refunds exploit (when deleting orders)
## Changed Files [#changed-files]
* client/cl-admin.lua
* client/cl-carlift.lua
* client/cl-dyno.lua
* client/cl-employees.lua
* client/cl-handling.lua
* client/cl-invoice.lua
* client/cl-lightcontroller.lua
* client/cl-locations.lua
* client/cl-main.lua
* client/cl-management.lua
* client/cl-minigames.lua
* client/cl-mods.lua
* client/cl-nitrous.lua
* client/cl-orders.lua
* client/cl-servicing.lua
* client/cl-stancer.lua
* client/cl-tablet.lua
* client/cl-tuning.lua
* client/cl-vehicleprops.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* fxmanifest.lua
* install/database/run.sql
* server/sv-main.lua
* server/sv-mods.lua
* server/sv-nitrous.lua
* server/sv-orders.lua
* server/sv-servicing.lua
* server/sv-tablet.lua
* server/sv-tuning.lua
* server/sv-vehicleprops.lua
* shared/main.lua
* web (entire folder)
* locales (translations modified)
# v1.0.12 (/mechanic/changelog/v1.0.12)
## Important patch [#important-patch]
Had no idea and no reproduction of this bug for weeks, but I found it this afternoon. I'm so sorry for everyone who has been suffering this bug. I was accidently overwriting my storage of vehicles' "base handling", meaning that servicing damage would be applied exponentially if you get in and out of your vehicle multiple times. Without this data, performance wouldn't reset when servicing a vehicle part & vehicles wouldn't go back to stock when removing performance parts.
## Fixes [#fixes]
* Stop `baseHandling` statebag being reset when getting in/out of a vehicle multiple times
## Changed Files [#changed-files]
* server/sv-vehicleprops.lua
# v1.0.2 (/mechanic/changelog/v1.0.2)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Fixes [#fixes]
* Fix issue where preview (ghost) vehicle would duplicate - huge thanks to
* Fix xenonHeadlights price `NaN` bug
* Fix QBCore `SetJobDuty`
* Fix `vehShared can't index a nil value` error
* Fix qb-skillbar firing callback immediately
* Electric vehicles now actually have electric vehicle servicing parts
## Sound [#sound]
*xSound has been completely removed as a dependency*, and the script now uses the native sound engine. If you installed xsound for this script, you can remove it. Massive thanks to for his help and for pointing me in the right direction!
## Locales [#locales]
I found even more missing locales... sorry! Again, you will need to do a diff check if you have modified locales. There weren't too many added!
## Config [#config]
No config changes!
## Changed Files [#changed-files]
* audiodirectory/jg\_mechanic.awc
* audiodirectory/jg\_mechanic.awc.xml
* audiodirectory/jg\_mechanic/hydraulic\_left.wav
* audiodirectory/jg\_mechanic/repair\_left.wav
* client/cl-carlift.lua
* client/cl-minigames.lua
* client/cl-mods.lua
* client/cl-tablet.lua
* data/audioexample\_sounds.dat54.rel.xml
* data/carcols\_gen9.meta
* data/carmodcols\_gen9.meta
* data/jg\_mechanic\_sounds.dat54.rel
* framework/cl-functions.lua
* framework/sv-functions.lua
* fxmanifest.lua
* web (entire folder)
* locales (translations modified)
# v1.0.3 (/mechanic/changelog/v1.0.3)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Improvements [#improvements]
* Native Qbox support (will also be autodetected with `Framework = "auto"`)
* Motorbikes can be serviced (with special UI), dyno'ed, tuned & have NOS installed if desired
* Disable ability for mechanic employees to self-service vehicles
* jg-vehiclemileage also has `Framework = "auto"` by default
## Fixes [#fixes]
* Fix bug where orders applied via the tablet would not stick (issue with `applyVehicleProps` statebag)
* Fix issue where livery could not be removed (vastly improved get/setVehicleProperties functions)
* Fix issue where all vehicles would gain a speed + traction boost when servicing is enabled
* Fix issue where vehicles *without* an engine bone could not be worked on
## Locales [#locales]
Added 2 more missing locales: `orderHasBeenPlaced` & `contactMechanicToFulfill`. Thanks for all the locale submissions so far, these will be added to the project by default at some point soon. If you're translated the script, it would be awesome if you were willing to share it with the community!
## Config [#config]
New config option: `Config.MechanicEmployeesCanSelfServiceMods = false`
## Changed Files [#changed-files]
* client/cl-carlift.lua
* client/cl-minigames.lua
* client/cl-mods.lua
* client/cl-servicing.lua
* client/cl-tablet.lua
* client/cl-tuning.lua
* client/cl-vehicleprops.lua
* framework/cl-functions.lua
* framework/main.lua
* framework/sv-functions.lua
* server/sv-invoice.lua
* server/sv-management.lua
* server/sv-orders.lua
* web (entire folder)
* locales (translations modified)
# v1.0.4 (/mechanic/changelog/v1.0.4)
## Fixes [#fixes]
* "Vehicle is not connected" when trying to install orders via the tablet (plate mismatch bug)
* Remove debugging print when opening the tablet
**No config, locale or other changes. Just replace the files listed below!**
## Changed Files [#changed-files]
* client/cl-mods.lua
* framework/cl-functions.lua
* web (entire folder)
# v1.0.5-hotfix1 (/mechanic/changelog/v1.0.5-hotfix)
Fix typo in `reapplyGTAPerformanceMods()`. Just replace the one file - no other changes.
## Changed Files [#changed-files]
* client/cl-handling.lua
# v1.0.5 (/mechanic/changelog/v1.0.5)
> **?? HIGHLY recommended update!**
## Handling [#handling]
Handling code has been completely reworked! Special thanks to for helping me get to the bottom of some of the more complex issues. Furthermore, any code that touches vehicle handling at all has been moved into a new, unencrypted file called `cl-handling.lua`. This should give you maximum flexibility in the case you have a complex vehicle setup and need to ensure there are no conflicts with our systems.
## Fixes [#fixes]
* Fix issue where tablet based tuning wouldn't save when the customer inserted the vehicle into a garage (statebag not replicated)
* Fix issue where only 1 inventory item would be taken when applying orders requiring multiple parts
## Other [#other]
* Added ped holding tablet animation when using the tablet
* Some tweaks to the carlift model + code to improve reliability and prevent prop falling through the map
## Changed Files [#changed-files]
* client/cl-carlift.lua
* client/cl-handling.lua
* client/cl-main.lua
* client/cl-minigames.lua
* client/cl-orders.lua
* client/cl-servicing.lua
* client/cl-stancer.lua
* client/cl-tablet.lua
* client/cl-tuning.lua
* client/cl-vehicleprops.lua
* fxmanifest.lua
* server/sv-carlift.lua
* shared/main.lua
# v1.0.6 (/mechanic/changelog/v1.0.6)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Economy [#economy]
* Pay with mechanic society fund if using self-service menu (as an employee)
* Option to not pay for an mechanic order (as an employee)
* Auto refunds player if deleting an order
## Fixes [#fixes]
* Bulletproof tyres now, like actually, exists as an option under "Tyres" (sorry)
* Fix bug where removing stance would display $0 but charge player
* Admin perms are now based on the ace `"command"` group and don't use the QB/ESX functions (fixes line 16 sv-functions.lua issue)
* Fix bug where servicing damage has no effect on vehicle
* Fix bug where low engine oil would have no effect on vehicle
* Fix bug where wheel rims would change in some cases
* Fix bug where neons, xenons & tyre smoke colours would not stick
* Extras bools are now flipped around so not enabled when disabled
* Fix bug where boats & planes would cause a stancing-related error message
* Fix bug where turbocharging would not stick when applied via tablet
* Fix bug where suspension height would change when applying modifications via a tablet order
* Fix bug where tablet prop would stay in hands during installation minigames
## Locales [#locales]
Some minor locale additions:
```lua
noPayment = "No Payment",
deleteOrderConfirmation = "Deleting this order will refund the customer in full, even if the order is partially fulfilled. Are you sure you want to delete it?",
cannotStanceVehicleType = "You can only stance cars",
```
## Config [#config]
No config changes!
## Changed Files [#changed-files]
* client/cl-dyno.lua
* client/cl-handling.lua
* client/cl-minigames.lua
* client/cl-mods.lua
* client/cl-nitrous.lua
* client/cl-orders.lua
* client/cl-servicing.lua
* client/cl-stancer.lua
* client/cl-tablet.lua
* client/cl-tuning.lua
* client/cl-vehicleprops.lua
* framework/sv-functions.lua
* install/inventory/qb-core-items.lua
* install/inventory/quasar-inventory-items.lua
* server/sv-mods.lua
* server/sv-orders.lua
* web (entire folder)
* locales (translations modified)
# v1.0.7 (/mechanic/changelog/v1.0.7)
Sorry the last update caused some issues with the self-service customise menu!
## Fixes [#fixes]
* Wheel rims not sticking
* Suspension height changing after purchase
* Motorcycles not being drivable when changing drivetrain
## Changed Files [#changed-files]
* client/cl-handling.lua
* client/cl-mods.lua
* client/cl-vehicleprops.lua
* shared/main.lua
# v1.0.8 (/mechanic/changelog/v1.0.8)
**?IMPORTANT SECURITY UPDATE - highly recommend upgrading as soon as possible**
## Fixes [#fixes]
* Fix withdraw/deposit negative value NUI exploit
* Fix issue where mechanic would keep order payment after refund
* Change spray can & nitrous props to prevent invalid model errors
## Changed Files [#changed-files]
* client/cl-minigames.lua
* props-animations.txt
* server/sv-management.lua
* server/sv-orders.lua
# v1.0.9 (/mechanic/changelog/v1.0.9)
Fix bug where custom tuning set to `requiresItem = false` would take funds from all society accounts due to a bad SQL query. Update *immediately* if you are using this option.
## Changed Files [#changed-files]
* server/sv-tuning.lua
# v1.1.1 (/mechanic/changelog/v1.1.1)
## Fixes [#fixes]
* Extras still not displaying correctly for some people
* Improve society banking functions & add Renewed, fd\_banking & okokBanking support
* Fix `SCRIPT ERROR: failed to resolve callback > waitFor`
* Fix incorrectly displayed `employeePermissionsError`
## Config [#config]
Added `Config.SocietyBanking` & more preset options for more control over third-party society account integrations.
## Changed Files [#changed-files]
* client/cl-mods.lua
* client/cl-vehicleprops.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* server/sv-initsql.lua
* server/sv-main.lua
* web (entire folder)
# v1.1.2 (/mechanic/changelog/v1.1.2)
## Fixes [#fixes]
* Fix issue where vehicle would be un-repaired upon exiting customisation menu
* Fix issue where players would get a free repair at an owned mechanic location
* Config option for plate change during customisation (`Config.ChangePlateDuringPreview = "PLATE" | false`)
* Horn previews play for their full length
* Fix lifts not working when a vehicle is not nearby
* (Potentially) fix issue where lifts would fall through the ground
## Customisation Menu Event (client) [#customisation-menu-event-client]
`TriggerEvent("jg-mechanic:client:open-customisation-menu", mechanicId, mechanicLabel)`
## Changed Files [#changed-files]
* client/cl-carlift.lua
* client/cl-fixing.lua
* client/cl-mods.lua
* client/cl-vehicleprops.lua
* framework/cl-functions.lua
* server/sv-carlift.lua
* server/sv-management.lua
* web (entire folder)
# v1.1.3 (/mechanic/changelog/v1.1.3)
## Fixes [#fixes]
* Fix stance not saving when using self-service mechanic
* Opt to change refresh rate of nearby stanced vehicles (lower `Config.StanceNearbyVehiclesFreqMs = 500`)
* Register job society accounts on start-up (ESX only)
## Changed Files [#changed-files]
* client/cl-lightcontroller.lua
* client/cl-mods.lua
* client/cl-stancer.lua
* client/cl-vehicleprops.lua
* framework/esx/sv-esx.lua
# v1.1 (/mechanic/changelog/v1.1)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Features [#features]
* Use your framework/third party scripts for mechanic ownership, society banking & employee hiring
* Detect if vehicles are electric automatically (>=b3258)
* Auto-run SQL setup
* Ghost 'preview' car removed in customisation mennu
## Fixes [#fixes]
* Fix ped invisibility & various other invisibility and exploits when using customisation menu
* Fix last extra not working
* Fix `nInitialDriveGears` having no effect when configured in config.tuning/config.servicing
* Reduce excessive data storing in mechanic\_vehicledata
* Fix plate change export creating duplicate rows in mechanic\_vehicledata
* Fix RGB chrome not applying as an order
* Remove nitrous app for electric vehicles
* Remove hardcoded permissions in favour of ACE (sv-functions.lua ->F ramework.Server.IsAdmin)
## Config [#config]
Just added `Config.AutoRunSQL = true` for new users. Don't add this if upgrading.
## Changed Files [#changed-files]
* client/cl-fixing.lua
* client/cl-handling.lua
* client/cl-locations.lua
* client/cl-management.lua
* client/cl-mods.lua
* client/cl-nitrous.lua
* client/cl-stancer.lua
* client/cl-vehicleprops.lua
* framework/cl-functions.lua
* framework/esx/cl-esx.lua
* framework/qb/cl-qb.lua
* framework/qbx/cl-qbx.lua
* framework/sv-functions.lua
* install/database/run.sql
* server/sv-admin.lua
* server/sv-employees.lua
* server/sv-fixing.lua
* server/sv-initsql.lua
* server/sv-invoice.lua
* server/sv-management.lua
* server/sv-mods.lua
* server/sv-orders.lua
* server/sv-tablet.lua
* server/sv-tuning.lua
* server/sv-vehicleprops.lua
* shared/main.lua
* web (entire folder)
# v1.2.0 (/mechanic/changelog/v1.2.0)
## Improvements [#improvements]
* Ability to name performance options (such as Race Brakes or Engine Level 1) in the config.mods.lua - see new `overrideOptions` in the default config (I recommend just using the new config file!)
## Fixes [#fixes]
* Fix infrequent bug where the server could take a different amount to the visual cart due to a bug in the server calculation
* Fix bulletproof tyres not sticking
* Fix exploit where item could be removed during repair
* Fix issue where removing pearlescence would add multiple items to basket
* Fix issue where tuning menus opened with event would fail at checkout
* Fix potential future issue where plates returned as `false` or `""` could be persisted to mechanic\_vehicledata
* Hide non-working HSW performance options from console GTA 5 via the new `overrideOptions` feature
* Fix issue where fuel would leak when using third-party garage scripts for storage
* Maybe fix UI going clear, but probably not I got inconsistent results
* Fix bug where `COUNT()` SQL query would fail on some installations
* Fix issue where a non-styled "0" would appear in tuning UI if the price is $0
* Support quadbikes wherever cars are supported (mainly tablet functions)
## The Future [#the-future]
I *am* looking to add more functionality to Mechanic in future updates and I've seen the requests - but getting this gigantic script completely stable has been a heck of a job in itself. Plus, I am working on some new scripts in the background ?? I appreciate your patience!
## Changed Files [#changed-files]
* client/cl-fixing.lua
* client/cl-lightcontroller.lua
* client/cl-mods.lua
* client/cl-nitrous.lua
* client/cl-tablet.lua
* client/cl-vehicleprops.lua
* server/sv-mods.lua
* server/sv-tablet.lua
* server/sv-vehicleprops.lua
* web (entire folder)
# v1.3.0 (/mechanic/changelog/v1.3.0)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Features [#features]
* Create shops & stashes directly in the config! (ox\_inventory & qb-inventory only)
* Configure employee commission for completed work orders & invoices (`Config.MechanicLocations -> commission`)
* Ability to invoice yourself for a work order
* Config event after an order has been placed (to add custom code/integrations)
* Restrict work order deletion to managers only (with `Config.RequireManagementForOrderDeletion = true`)
* Use custom modType labels in tuning menu (with `Config.UseCustomNamesInTuningMenu = true`)
* Add artifact issues check on start (uses artifacts.jgscripts.com)
## Fixes [#fixes]
* Fix turbo costing a different amount on the server
* Changed HP to RPM in dyno graph
* Stop repairs being started while in vehicle
* Window tint modIndex defaulted to 0
* Fix issue where updating primary & secondary colours would be charged when unchanged
* HUD no longer hides when in the tablet
## Integrations [#integrations]
* nox\_notify support
* qs-inventory support
## Config changes [#config-changes]
* New shops/stashes boilerplate in `Config.MechanicLocations`
* `Config.Target` (required)
* `Config.UseSocietyFund` (required)
* `Config.UseCustomNamesInTuningMenu` (optional)
* `Config.RequireManagementForOrderDeletion` (optional)
## Changed Files [#changed-files]
* client/cl-fixing.lua
* client/cl-lightcontroller.lua
* client/cl-locations.lua
* client/cl-main.lua
* client/cl-mods.lua
* client/cl-shops-stashes.lua
* client/cl-tablet.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* server/sv-invoice.lua
* server/sv-management.lua
* server/sv-mods.lua
* server/sv-orders.lua
* server/sv-shops-stashes.lua
* server/sv-version-check.lua
* shared/main.lua
* web (entire folder)
* config (config files changed)
# v1.3.1 (/mechanic/changelog/v1.3.1)
## Ease of Use [#ease-of-use]
* Added markers for shops/stashes to default config (optional)
## Fixes [#fixes]
* Fixed ox\_inventory stashes integration
* `Config.UseSocietyFunds = true` *actually* charges the society now
* Added missing `Config.PlayerBalance` to select "cash" or "bank" for shops
* Removed `SetVehicleAutoRepairDisabled` from `setVehicleProperties`
## Changed Files [#changed-files]
* client/cl-shops-stashes.lua
* client/cl-vehicleprops.lua
* server/sv-shops-stashes.lua
* config (config files changed)
# v1.4.0 (/mechanic/changelog/v1.4.0)
**Optional update, integrations for an upcoming script only**
* JG Handling Pro exports & fix conflicts
## Changed Files [#changed-files]
* client/cl-handling.lua
* shared/constants.lua
# v1.4.1 (/mechanic/changelog/v1.4.1)
## Fixes [#fixes]
* Fixed error when trying to use dyno
## Changed Files [#changed-files]
* client/cl-dyno.lua
* client/cl-handling.lua
# v1.5 (/mechanic/changelog/v1.5.0)
Manual Transmissions in FiveM! You can install the manual transmissions via the JG Mechanic tablet, which can be set up to require an item or just cost money from the mechanic society. If you don't shift gears properly, your engine will take damage.
Free update for existing customers of JG Mechanic.
Purchase it here: [https://jgscripts.com/scri](https://jgscripts.com/scri)...
## Changed Files [#changed-files]
* client/cl-handling.lua
* client/cl-mods.lua
* client/cl-tablet.lua
* client/cl-tuning.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* fxmanifest.lua
* server/sv-orders.lua
* shared/constants.lua
* shared/main.lua
* web (entire folder)
* config (config files changed)
# v1.5.1 (/mechanic/changelog/v1.5.1)
## Artwork Inventory Icons [#artwork-inventory-icons]
All new inventory icons from the awesome team at [Artwork Inventory](https://artwork-inventory.tebex.io/)! Find them at `/install/inventory/images`.
* +1 new icon, `manual_gearbox.png`
## Fixes [#fixes]
* Fix manual transmission being overwritten by other tunes
* Allow vec4 in shops/stashes to position peds in the desired direction
* Remove peds on resource stop/restart
* Add jobs injection net event for jaksam's job creator (QBCore only)
* Fix undescriptive `GetPedType` error when invalid ped is used
## Integrations [#integrations]
* tgg-banking for society banking
## Changed Files [#changed-files]
* client/cl-handling.lua
* client/cl-main.lua
* client/cl-shops-stashes.lua
* framework/qb/cl-qb.lua
* framework/sv-functions.lua
* config (config files changed)
# v1.5.2 (/mechanic/changelog/v1.5.2)
## Fixes [#fixes]
* Fix `GET_VEHICLE_TOP_SPEED_MODIFIER` console error
* Rename duplicate named callback
* Update open inventory export for qs-inventory
## Changed Files [#changed-files]
* client/cl-handling.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* fxmanifest.lua
# v1.6.0 (/mechanic/changelog/v1.6.0)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
**?? This is an important security update - please update as soon as possible**
## Security [#security]
* Fix exploits where multiple clients can perform the same actions, causing duplication of money and/or items
* Fix exploit where turning off the internet during a network event could be used to duplicate money and/or items
* Refactor `buy-item` event, which has been abused to gain free items
## Fixes [#fixes]
* Fix issue where self invoicing would freeze your vehicle
* Added server-sided pagination to orders app to improve performance
* Change plate on the server side when using tuning menu, so it's not blocked by anti-cheat scripts
## Integrations [#integrations]
* Add integration with Wizating-Laptop
## Changed Files [#changed-files]
* client/cl-handling.lua
* client/cl-invoice.lua
* client/cl-mods.lua
* client/cl-orders.lua
* client/cl-shops-stashes.lua
* client/cl-tablet.lua
* framework/sv-functions.lua
* server/sv-invoice.lua
* server/sv-mods.lua
* server/sv-orders.lua
* server/sv-servicing.lua
* server/sv-shops-stashes.lua
* server/sv-tablet.lua
* server/sv-tuning.lua
* web (entire folder)
* config (config files changed)
# v1.6.1 (/mechanic/changelog/v1.6.1)
## Fixes [#fixes]
* Improved robustness of get/set vehicle properties functions
* Fix issue where custom vehicle handling would be lost when exiting & re-entering the vehicle
* Fix some strange UI state bugs
* Allow movement of wheels (A/D keys) in the tuning freecam
## Changed Files [#changed-files]
* client/cl-handling.lua
* client/cl-invoice.lua
* client/cl-mods.lua
* client/cl-vehicleprops.lua
* framework/cl-functions.lua
* web (entire folder)
# v1.6.2 (/mechanic/changelog/v1.6.2)
## > ?? Important Security Update [#--important-security-update]
Fixes exploit with `mark-order-fulfilled`
## Changed Files [#changed-files]
* server/sv-orders.lua
# v1.6.3 (/mechanic/changelog/v1.6.3)
## Integrations [#integrations]
* Add support for lation\_ui [\[buy it here\]](https://lationscripts.com/product/modern-ui)
## Fixes [#fixes]
* Parts being saved to `Config.ChangePlateDuringPreview` if not set to "PREVIEW"
* Fix "Another mechanic is connected to this vehicle" when another mechanic isn't connected
* Fix issue where doors, windows or tyres would sometimes still be damaged after repairing in the menu
## Config [#config]
* Added new config option for disabling "No Payment" option -> `Config.DisableNoPaymentOptionForEmployees = true/false`
* Added option to use lation\_ui for shop menus -> `Config.Menus = "ox"/"lation_ui"`
## Changed Files [#changed-files]
* client/cl-mods.lua
* client/cl-shops-stashes.lua
* client/cl-tablet.lua
* client/cl-vehicleprops.lua
* framework/cl-functions.lua
* server/sv-mods.lua
* server/sv-vehicleprops.lua
* web (entire folder)
* config (config files changed)
# v1.6.4 (/mechanic/changelog/v1.6.4)
## Fixes [#fixes]
* RGB paint type not saving in garage
* Stance not saving if preview plate config opt is enabled
* GTA performance mods (engine lvl 4, transmissions, etc) being lost/not saving/not applying when serviced
* Improved reliability of reapplying custom tuning, stancing & servicing when driving out of garage
* Turbo being lost when applied at self service location
* Fix xenons, neons & tyre smoke being in cart when unchanged
* All nearby clients are no longer notified when a vehicle needs servicing
* Fix issue where, in some cases, baseHandling & servicingData statebags were being set by all nearby clients
* Reduce number of c->s network requests
* Stancing system has been made much smoother and less glitchy for better immersion
* Fix exploit where you could get a free vehicle stance by timing the thread updates
* Fix mileage in km not rounded in servicing app
## Changed Files [#changed-files]
* client/cl-handling.lua
* client/cl-main.lua
* client/cl-mods.lua
* client/cl-servicing.lua
* client/cl-stancer.lua
* client/cl-vehicleprops.lua
* server/sv-vehicleprops.lua
* web (entire folder)
# v1.6.5 (/mechanic/changelog/v1.6.5)
My apologies for the messy last update!
## Fixes [#fixes]
* Disabled `use_experimental_fxv2_oal`; as it is causing sporadic issues with vehicle cosmetics & paint (as well as extras, windows & doors, but this was my fault)
* Fix stance returning to it's previously cached value
* Fix pt.lua locale file
## Changed Files [#changed-files]
* client/cl-stancer.lua
* client/cl-vehicleprops.lua
* fxmanifest.lua
# v1.6.6 (/mechanic/changelog/v1.6.6)
## Fixes [#fixes]
* Fix camera bug, where other scripts would display a camera from JG Mechanic
* Refactored nitrous to prevent exploit where spamming the hotkey could give you more speed with less usage
* Fix bug with lation\_ui integration
* Improved UX when refilling nitrous bottles via the tablet (+ fixes spam refill exploit)
* Fix issue where opening the tablet twice (usually via F8) would disable tablet until script is restarted
* Display pretty item label in successful shop purchase notification
* Fix free colour exploit when changing to a different colour, and then changing back
* Remove additional comma in ESX run-sql
## Integrations [#integrations]
* Society banking support for Wasabi Banking
## Changed Files [#changed-files]
* client/cl-cameras.lua
* client/cl-mods.lua
* client/cl-nitrous.lua
* client/cl-tablet.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* server/sv-nitrous.lua
* server/sv-shops-stashes.lua
* web (entire folder)
* config (config files changed)
# v1.7.0 (/mechanic/changelog/v1.7.0)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Features [#features]
* Rewritten webhook & logging system with support for lib.logger and more -> [https://docs.jgscripts.com/mechanic/debug-and-event-logging](https://docs.jgscripts.com/mechanic/debug-and-event-logging)
## Fixes [#fixes]
* Codebase refactoring to improve overall script stability
* Only allow managers to see management app
* Players can enter and drive vehicle while using a repair kit
* Vehicle being stanced to default even with stanced disabled (overwriting suspension)
* Tablet will not reopen after invoicing yourself for work
* Plate within vehicle props not trimmed
* Require mechanic to get in vehicle after applying order for improved sync reliability
* Nitrous app visible for motorcycles
* Ability to get a free stance upgrade by abusing thread `Wait` in mods menu
## Integrations [#integrations]
* Support Snipe banking
## Changed Files [#changed-files]
* client/cl-cameras.lua
* client/cl-carlift.lua
* client/cl-dyno.lua
* client/cl-fixing.lua
* client/cl-handling.lua
* client/cl-invoice.lua
* client/cl-lightcontroller.lua
* client/cl-locations.lua
* client/cl-main.lua
* client/cl-minigames.lua
* client/cl-mods.lua
* client/cl-nitrous.lua
* client/cl-orders.lua
* client/cl-servicing.lua
* client/cl-shops-stashes.lua
* client/cl-stancer.lua
* client/cl-tablet.lua
* client/cl-tuning.lua
* client/cl-vehicleprops.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* fxmanifest.lua
* server/sv-admin.lua
* server/sv-employees.lua
* server/sv-fixing.lua
* server/sv-initsql.lua
* server/sv-invoice.lua
* server/sv-logs.lua
* server/sv-main.lua
* server/sv-management.lua
* server/sv-mods.lua
* server/sv-nitrous.lua
* server/sv-orders.lua
* server/sv-servicing.lua
* server/sv-shops-stashes.lua
* server/sv-tablet.lua
* server/sv-tuning.lua
* server/sv-vehicleprops.lua
* server/sv-webhooks.lua
* shared/main.lua
* web (entire folder)
* config (config files changed)
# v1.7.1 (/mechanic/changelog/v1.7.1)
## Fixes [#fixes]
* No longer have to get in the vehicle after every part installed, just once when disconnecting vehicle
* Fix issue where installing orders would get stuck when some locales are missing
* Reduced sev of no shops/stashes log message to "DEBUG"
## Changed Files [#changed-files]
* client/cl-orders.lua
* client/cl-shops-stashes.lua
* client/cl-tablet.lua
* web (entire folder)
# v1.7.2 (/mechanic/changelog/v1.7.2)
## Integrations [#integrations]
* Wasabi Banking
## Bugs [#bugs]
* Vehicles get washed when fixing them within the tuning menu
* Repair cars just by entering menu
* Vehicles locking after repair has been completed
* Damage check not checking tyres
* Fix ox\_lib progress prop nil model error during mechanic actions
## Changed Files [#changed-files]
* client/cl-fixing.lua
* client/cl-mods.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
# v1.8.0 (/mechanic/changelog/v1.8.0)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Security [#security]
* No permission check across some hire employee events & callbacks
## Features [#features]
* Ability to mix price and percentVehVal instead of it being either one [\[learn more\]](https://docs.jgscripts.com/mechanic/pricing)
## Integrations [#integrations]
* Add support for Jaksam Inventory
## Bugs [#bugs]
* Mod menu can sometimes not be defaulted, breaking it until restart
* Fix vehicle performance upgrades being lost when the driver re-enters the vehicle
* Fix balance errors when a Wasabi society account is missing
* Fix ox\_lib logger tag warnings from debug statebag logs
## Config [#config]
* Added `Config.ModsPricesIncludeFixedPrice = false`
## Changed Files [#changed-files]
* client/cl-employees.lua
* client/cl-handling.lua
* client/cl-mods.lua
* client/cl-shops-stashes.lua
* client/cl-vehicleprops.lua
* framework/cl-functions.lua
* framework/sv-functions.lua
* server/sv-employees.lua
* server/sv-logs.lua
* server/sv-mods.lua
* server/sv-orders.lua
* server/sv-servicing.lua
* server/sv-shops-stashes.lua
* server/sv-tablet.lua
* server/sv-vehicleprops.lua
* web (entire folder)
* config (config files changed)
# Custom Inventory System (/mechanic/inventory-setup/custom-inventory-system)
Use this page to connect an inventory that isn't built into JG Mechanic. You will add an adapter to the editable framework files. The adapter passes JG Mechanic's item and stash data to your inventory's API.
You need the API documentation for your inventory. It must provide server-side ways to register usable items, count items, add items and remove items. You also need a client or server API for opening stashes.
A server event that doesn't return a value can't be used to count items. `Framework.Server.HasItem` needs the count before it can continue.
## What the adapter must do [#what-the-adapter-must-do]
JG Mechanic already provides the values in the table below. Your code only needs to pass them to the matching inventory API and normalise its result.
| JG Mechanic function | Runs on | Values available | Required result |
| ------------------------------------- | ------- | ----------------------------- | ------------------------------------------------------------------- |
| `Framework.Server.RegisterUsableItem` | Server | `itemName`, `cb` | Run `cb(src)` when the player uses the item |
| `Framework.Server.HasItem` | Server | `src`, `itemName`, `qty` | Set `itemCount` to the amount the player has |
| `Framework.Server.GiveItem` | Server | `src`, `itemName`, `qty` | Add the items, or return `false` when the inventory rejects them |
| `Framework.Server.RemoveItem` | Server | `src`, `itemName`, `qty` | Remove the items, or return `false` when the inventory rejects them |
| `Framework.Client.OpenInventoryStash` | Client | `stashId`, `stashName` | Open the named stash |
| `Framework.Server.OpenInventoryStash` | Server | `src`, `stashId`, `stashName` | Open the named stash when your inventory uses a server API |
`src` is the player's server ID. Keep that value separate from a character ID, licence or inventory owner ID unless your inventory API specifically requires one of those values.
The examples use these placeholder export names:
* `CreateUsableItem`
* `GetItemCount`
* `AddItem`
* `RemoveItem`
* `OpenStash`
* `RegisterStash`
Replace each placeholder with the exact export or event from your inventory's documentation. Keep the JG Mechanic variable names shown in the examples.
## Build the adapter [#build-the-adapter]
### Find the inventory APIs [#find-the-inventory-apis-step]
Open your inventory's API documentation and find the server functions for:
* registering a usable item
* getting an item or item count for a player
* adding an item
* removing an item
Then find its stash documentation. Check whether stashes open on the client or server and whether they must be registered before they can open.
Pay attention to argument order and return values. For example, your inventory documentation might show:
```lua
local count = exports["inventory-name"]:GetItemCount(playerId, item)
```
The same call inside JG Mechanic uses its existing variable names:
```lua
itemCount = exports[Config.CustomInventory]:GetItemCount(src, itemName) or 0
```
Here, `playerId` becomes `src` and `item` becomes `itemName`. `or 0` makes a missing item count as zero instead of `nil`.
### Configure the inventory resource [#configure-the-inventory-resource-step]
Back up these files before editing them:
* `config/config.lua`
* `framework/sv-functions.lua`
* `framework/cl-functions.lua`
* `server/sv-shops-stashes.lua`
Keep the backup outside the `jg-mechanic` folder.
Open `config/config.lua`. Set `Config.Inventory` to `custom`, then add `Config.CustomInventory` directly below it:
```lua title="config/config.lua"
Config.Inventory = "custom"
Config.CustomInventory = "your-inventory-folder"
```
Replace `your-inventory-folder` with the exact resource folder name. `Config.CustomInventory` is used by the adapter code below. JG Mechanic doesn't read it anywhere else.
Make sure the inventory starts before JG Mechanic in `server.cfg`:
```ini title="server.cfg"
ensure your-inventory-folder
ensure jg-mechanic
```
If either resource is started through a folder such as `ensure [standalone]`, keep that existing line. The inventory still needs to start first.
### Register usable items [#register-usable-items-step]
Open `framework/sv-functions.lua` and find:
```lua title="framework/sv-functions.lua"
function Framework.Server.RegisterUsableItem(itemName, cb)
```
Inside that function, find the existing branch that starts with:
```lua title="framework/sv-functions.lua"
elseif Config.Framework == "QBCore" or Config.Inventory == "qb-inventory" then
```
Paste this block directly above that branch:
```lua title="framework/sv-functions.lua"
elseif Config.Inventory == "custom" then
exports[Config.CustomInventory]:CreateUsableItem(itemName, function(src)
cb(src)
end)
```
Replace `CreateUsableItem` with your inventory's server export. The callback must pass the player's server ID to `cb`.
If your inventory supplies the server ID in a table, adapt only the callback. For example, if it calls your handler with `data.source`:
```lua title="framework/sv-functions.lua"
elseif Config.Inventory == "custom" then
exports[Config.CustomInventory]:CreateUsableItem(itemName, function(data)
cb(data.source)
end)
```
Keep the new `elseif` aligned with the existing `elseif` lines. Don't add another `end`.
### Return the item count [#return-the-item-count-step]
In `framework/sv-functions.lua`, find:
```lua title="framework/sv-functions.lua"
function Framework.Server.HasItem(src, itemName, qty)
```
Inside that function, paste this block directly above the existing QBCore branch:
```lua title="framework/sv-functions.lua"
elseif Config.Inventory == "custom" then
itemCount = exports[Config.CustomInventory]:GetItemCount(src, itemName) or 0
```
Replace `GetItemCount` and change the argument order if your inventory requires it. This branch must set `itemCount` to a number. Don't return from the branch. The rest of `Framework.Server.HasItem` compares `itemCount` with `qty` and displays the missing-item notification.
If your inventory returns an item table instead of a number, read the amount from that table:
```lua title="framework/sv-functions.lua"
elseif Config.Inventory == "custom" then
local item = exports[Config.CustomInventory]:GetItem(src, itemName)
itemCount = item and item.amount or 0
```
Replace `GetItem` and `item.amount` with the names used by your inventory.
If the inventory only returns `true` or `false` from a `HasItem` export, pass `qty` to it and convert the result:
```lua title="framework/sv-functions.lua"
elseif Config.Inventory == "custom" then
local hasItem = exports[Config.CustomInventory]:HasItem(src, itemName, qty)
itemCount = hasItem and qty or 0
```
### Add items [#add-items-step]
In `framework/sv-functions.lua`, find:
```lua title="framework/sv-functions.lua"
function Framework.Server.GiveItem(src, itemName, qty)
```
Inside that function, paste this block directly above the existing QBCore branch:
```lua title="framework/sv-functions.lua"
elseif Config.Inventory == "custom" then
local added = exports[Config.CustomInventory]:AddItem(src, itemName, qty)
if added == false then return false end
```
Replace `AddItem` and change the argument order to match your inventory.
The `== false` check is intentional. It supports inventories that return `false` when full and inventories that don't return a value on success. If your API uses a different failure value, convert that result to `false` here.
Don't add `return true` to this branch. The function already returns `true` after the inventory branches.
### Remove items [#remove-items-step]
In `framework/sv-functions.lua`, find:
```lua title="framework/sv-functions.lua"
function Framework.Server.RemoveItem(src, itemName, qty)
```
This function already calls `Framework.Server.HasItem` before trying to remove anything. Inside the inventory branches, paste this block directly above the existing QBCore branch:
```lua title="framework/sv-functions.lua"
elseif Config.Inventory == "custom" then
local removed = exports[Config.CustomInventory]:RemoveItem(src, itemName, qty)
if removed == false then return false end
```
Replace `RemoveItem` and change the argument order to match your inventory. As with `GiveItem`, convert the inventory's failure result to `false` and let the existing function return `true` after a successful removal.
### Open stashes [#open-stashes-step]
Use one of the two options below. Don't add both.
#### Client API [#client-api]
Use this option when your inventory documentation opens a stash from client code.
Open `framework/cl-functions.lua` and find:
```lua title="framework/cl-functions.lua"
function Framework.Client.OpenInventoryStash(stashId, stashName)
```
Paste this block directly above the final `else` in that function:
```lua title="framework/cl-functions.lua"
elseif Config.Inventory == "custom" then
exports[Config.CustomInventory]:OpenStash(stashId, stashName)
```
Replace `OpenStash` and its arguments with the client API from your inventory. Use `stashId` as the unique stash identifier and `stashName` as the display label.
#### Server API [#server-api]
Use this option when your inventory opens a stash from server code.
In `framework/cl-functions.lua`, paste this block directly above the final `else` in `Framework.Client.OpenInventoryStash`:
```lua title="framework/cl-functions.lua"
elseif Config.Inventory == "custom" then
TriggerServerEvent("jg-mechanic:server:open-inventory-stash", stashId, stashName)
```
The event already exists in JG Mechanic. Don't register another copy.
Then open `framework/sv-functions.lua` and find:
```lua title="framework/sv-functions.lua"
function Framework.Server.OpenInventoryStash(src, stashId, stashName)
```
Paste this block directly above the final `else`:
```lua title="framework/sv-functions.lua"
elseif Config.Inventory == "custom" then
exports[Config.CustomInventory]:OpenStash(src, stashId, stashName)
```
Replace `OpenStash` and its arguments with the server API from your inventory. If the inventory uses a server event, replace the export line with its `TriggerEvent` call.
### Register stashes if required [#register-stashes-if-required-step]
Skip this step when your inventory creates a stash the first time it opens.
If the inventory has a separate `RegisterStash` API, open `server/sv-shops-stashes.lua`. Find the `CreateThread` under the `-- Stashes` comment. It currently registers stashes for `ox_inventory`.
Add this `elseif` branch after the complete `ox_inventory` branch and before the `end` that closes the inventory check:
```lua title="server/sv-shops-stashes.lua"
elseif Config.Inventory == "custom" then
for mechanicId, location in pairs(Config.MechanicLocations) do
for stashIndex, stash in ipairs(location.stashes or {}) do
local stashId = mechanicId .. "_" .. stash.name:gsub(" ", "_"):lower() .. "_" .. stashIndex
local label = stash.name
local slots = stash.slots or 50
local maxWeight = stash.weight or 50000
exports[Config.CustomInventory]:RegisterStash(stashId, label, slots, maxWeight)
end
end
```
Replace `RegisterStash` and its argument order with the inventory's server API. The generated `stashId` matches the ID passed to `Framework.Client.OpenInventoryStash`.
Some inventories require coordinates, an owner or allowed groups when registering a stash. The current stash coordinates are available as `stash.coords`. Add the other values only when your inventory API requires them.
### Add the items and images [#add-the-items-and-images-step]
The source item definitions are in `jg-mechanic/install/inventory`. Start with the file closest to your inventory's item format:
* `ox_inventory-items.lua` for table-based item definitions
* `qb-core-items.lua` for QBCore-style item definitions
* `esx-items.sql` for database item definitions
* `quasar-inventory-items.lua` for Quasar-style definitions
Convert the entries into your inventory's format. Keep each item name unchanged because JG Mechanic refers to those exact names in code.
If your inventory has a usable-item flag, enable it for:
* `lighting_controller`
* `stancing_kit`
* `nitrous_bottle`
* `cleaning_kit`
* `repair_kit`
* `duct_tape`
* `mechanic_tablet`
Copy every image from `jg-mechanic/install/inventory/images` into the item-image folder used by your inventory.
### Check the adapter [#check-the-adapter-step]
Before restarting, check each file:
* `config/config.lua` contains `Config.Inventory = "custom"` and the correct resource folder in `Config.CustomInventory`.
* `framework/sv-functions.lua` has custom branches in `RegisterUsableItem`, `HasItem`, `GiveItem` and `RemoveItem`.
* `framework/cl-functions.lua` has one custom stash branch.
* `framework/sv-functions.lua` also has a custom stash branch when you chose the server option.
* `server/sv-shops-stashes.lua` has a custom registration branch only when the inventory requires one.
* Every new `elseif` lines up with the existing branches and remains inside its function.
* You didn't add or remove an `end` around an inventory branch.
* Every placeholder export name has been replaced unless it is the real name used by your inventory.
Search all four files for `Config.Inventory == "custom"`. You should find at least five branches: four server item branches and one client stash branch.
### Test each operation [#test-each-operation-step]
Fully restart your server and watch the console for errors from `jg-mechanic` or your inventory.
Test the adapter in this order:
1. Give yourself a `repair_kit` and use it. The repair action should start. This tests usable-item registration and the callback's `src` value.
2. Give yourself one `engine_oil`, then service a vehicle. JG Mechanic should detect it and remove it. This tests both the count and remove calls.
3. Buy an item from a mechanic shop. The item should appear in your inventory. Fill the inventory first and repeat the purchase to confirm a rejected add returns `false`.
4. Open a mechanic stash. Deposit an item, close the stash, then open it again. The same item should still be there.
5. Restart the server and open the same stash again. If it is empty or has a different ID, compare the ID used by the registration and open calls.
## Troubleshooting [#troubleshooting]
The export name is wrong, the inventory isn't started yet or the API runs on the other side. Confirm the spelling and whether the documentation labels it client or server. Make sure the inventory starts before `jg-mechanic`.
Log or print the value assigned to `itemCount` inside the custom `HasItem` branch. It must be a number. Check whether the inventory expects `itemName` before `src`, or returns an item table instead of a count.
The custom `RegisterUsableItem` callback must call `cb` with the player's server ID. Check the callback data from your inventory and convert it to `cb(src)`.
Check the custom branch in `Framework.Server.RemoveItem`. Confirm its argument order and make sure the inventory's failure result is converted to `false`.
Confirm `Config.Inventory` is set to `custom`. The client branch belongs directly above the final `else` in `Framework.Client.OpenInventoryStash`. If you chose the server option, add the matching branch above the final `else` in `Framework.Server.OpenInventoryStash`.
Use `stashId` as the unique ID in both the registration and open calls. Don't use `stashName` as the ID. The display name can change, but the ID must stay the same.
The custom branch is outside the existing `if` chain or an extra `end` was added. Move the custom `elseif` directly above the named existing branch or final `else`. Keep it aligned with the other `elseif` lines.
# esx_inventory (/mechanic/inventory-setup/esx_inventory)
## Items [#items]
1. Open your preferred database handler (HeidiSQL or PHPMyAdmin).
2. Open the `esx-items.sql` file and run it.
3. Restart the server.
# ox_inventory (/mechanic/inventory-setup/ox_inventory)
## Items [#items]
1. Go to `jg-mechanic\install\inventory\ox_inventory-items.lua` and copy the whole list **without** `Config.Items = {` and `}` at lines **3** and **190**.
2. Go to the bottom of `ox_inventory\data\items.lua`.
3. Paste the items before the last `}`.\
## Images [#images]
1. Go to `jg-mechanic\install\inventory\images` and copy all the images.
2. Locate the `ox_inventory\web\images` folder.
3. Paste the images in there.
# qb-inventory (/mechanic/inventory-setup/qb-inventory)
## Items [#items]
1. Go to `jg-mechanic\install\inventory\qb-core-items.lua` and copy the whole list **without** `Config.Items = {` and `}` at lines **3** and **53**.
2. Go to the bottom of `qb-core\shared\items.lua`.
3. Paste all the items before the last `}`.\\
## Images [#images]
1. Go to `jg-mechanic\install\inventory\images` and copy all the images.
2. Locate the `qb-inventory\html\images` folder.
3. Paste the images in there.
# ESX Framework Jobs (/mechanic/job-setup/esx-framework-jobs)
You only need to follow these steps if you're using **`Config.UseFrameworkJobs = true`**.
Follow these steps for all owned mechanics.
1. Get the job name from `config.lua`.
2. Go to your database.
3. Open the `addon_account` table.
4. Right click and press Insert row.
5. Set the name to `society_[JOBNAME]`.
For example: job name `lscustoms` would be `society_lscustoms`.\
Set the `shared` column to 1.
6. Open the `addon_account_data` table.
7. Right click and press Insert row.
8. Set the `account_name` to the same as before, e.g. `society_lscustoms`.\
Then set `money` to 0 and leave the rest as it is.
# Job Setup (/mechanic/job-setup)
With JG Mechanic, you can use our built-in job system, which includes a society bank account, employees and more. Or you can use your framework's job system with a third-party society banking script.
Only mechanic locations set as `"owned"` can be linked to a job.
## Built-in job system [#built-in-job-system]
Set `Config.UseFrameworkJobs = false`
Access the built-in society banking, employee management and other features through the tablet. With `Config.AdminsHaveEmployeePermissions = true` you can log into the tablet as an admin (`/tablet`) and manage the mechanic locations yourself. The preferred way, though, is to set an *initial owner* of the mechanic, so they have access to the tablet and can hire further employees themselves. This could take the form of them 'buying' the business.
To set the initial owner, use `/mechanicadmin` and click "Set owner" on the corresponding location (remember it must be set to `"owned"` in `config.lua` and NOT `"self-service"`).
The new owner of the mechanic location can use `/tablet` to log in and manage the business themselves. All functions can be accessed through the 'Management' app.
## Framework job system (built into Qbox/QBCore/ESX) [#framework-job-system-built-into-qboxqbcoreesx]
Set `Config.UseFrameworkJobs = true`
The location MUST be set up with a type of `"owned"` (NOT `"self-service"`), and it needs to have a unique `job` set. The job is the corresponding job name in your framework. If you want unique businesses, you'll need to create unique job names within your framework. Here is an example of what a correctly configured location should include:
```lua
type = "owned",
job = "mechanic",
jobManagementRanks = {3, 4}, -- which ranks should have access to 'ownership' perms
```
Once set up correctly, changing your job to the configured job name will allow you to log in to the tablet (`/tablet`).
JG Mechanic doesn't show its built-in Management app when you use framework jobs. Hire employees, remove employees and change their grades through your framework's boss menu or a compatible third-party boss menu.
The banking resource selected by `Config.SocietyBanking` owns the society account and its balance. JG Mechanic uses that integration to read, add and remove money. Follow [Society Banking](/mechanic/job-setup/society-banking) to create and test the account for each unique mechanic job.
## Society banking (only with `Config.UseFrameworkJobs = true`) [#society-banking-only-with-configuseframeworkjobs--true]
JG Mechanic doesn't create accounts in third-party banking resources. Every unique `job` used by an owned mechanic location needs a matching society account.
Follow [Society Banking](/mechanic/job-setup/society-banking) before taking work orders. It covers account creation, supported banking resources and the checks to run before opening the mechanic.
# Society Banking (/mechanic/job-setup/society-banking)
Use this page when `Config.UseFrameworkJobs = true`. The built-in job system uses JG Mechanic's own accounts and doesn't need a separate banking resource.
JG Mechanic uses the `job` from each owned mechanic location as its society account name. A location with `job = "mechanic"` needs a society account named `mechanic`.
JG Mechanic doesn't create accounts in third-party banking resources. Create an account for every unique mechanic job before taking work orders. Account names are case-sensitive.
## Before you take work orders [#before-you-take-work-orders]
Complete these checks for every owned mechanic location:
1. Open `config/config.lua` and find the location's `job` value.
2. Check that the same job exists in your framework.
3. Create a society account with the exact job name in your banking resource.
4. Start the banking resource before `jg-mechanic` in `server.cfg`.
5. Set `Config.SocietyBanking` to `"auto"` or the exact supported value for your banking resource.
6. Check that the account can return its balance, receive money and remove money.
Locations can share one account by using the same `job`. Repeat the account setup for every different job name.
JG Mechanic supports `"okokBanking"`, `"fd_banking"`, `"Renewed-Banking"`, `"tgg-banking"`, `"wasabi_banking"`, `"qb-banking"`, `"qb-management"` and `"esx_addonaccount"`.
## TGG Banking [#tgg-banking]
1. Give your account admin permission in TGG Banking.
2. Open the banking interface and select **Admin Actions**.
3. Create a society account.
4. Set **Society Name** to the exact `job` from `config/config.lua`, such as `mechanic`. **Display Name** can be any player-facing name.
Repeat this for every unique mechanic job.
See the [TGG Banking society account setup](https://www.teamsgg.dev/docs/paid-scripts/banking/installation#society-accounts).
## okokBanking [#okokbanking]
1. Open `okokBanking/config.lua`.
2. Find `Config.Societies`.
3. Add every unique mechanic job. Keep your existing entries.
For current okokBanking versions, add the job and the grades that can manage its account:
```lua title="okokBanking/config.lua"
["mechanic"] = {0, 1, 2, 3, 4},
```
Replace `mechanic` with the exact `job` from `config/config.lua`. Older okokBanking versions may list society names without grades. Use the same format as the other entries in your installed version.
See the [okokBanking configuration reference](https://docs.okokscripts.io/scripts/okokbankingv2/config-file).
## ESX [#esx]
Follow [ESX Framework Jobs](/mechanic/job-setup/esx-framework-jobs) to create the required `esx_addonaccount` records for every mechanic job.
## Connect another banking resource [#connect-another-banking-resource]
Setting an unsupported resource name in `Config.SocietyBanking` doesn't add support. Your banking resource needs server-side functions or exports that can:
* return a society account's balance as a number
* add money to a society account
* remove money from a society account
Connect these operations to the following functions in `framework/sv-functions.lua`:
```lua title="framework/sv-functions.lua"
function Framework.Server.GetSocietyBalance(society, societyType)
function Framework.Server.PayIntoSocietyFund(societyName, societyType, amount)
function Framework.Server.RemoveFromSocietyFund(societyName, societyType, amount)
```
## Test the account [#test-the-account]
Fully restart your server, then run these checks with a small paid work order:
1. Open the mechanic tablet and check that it shows the society account's current balance.
2. Place a paid work order and check that the same account receives the payment.
3. Delete the unfulfilled work order and check that the payment is removed from the society account and refunded to the customer.
The society account needs enough money to refund the work order before it can be deleted.
## Troubleshooting [#troubleshooting]
JG Mechanic removes the order payment from the society account before refunding the customer. Check that the account exists, its name matches the location's `job`, and its balance covers the refund. If those are correct, check that your banking integration can return the balance and remove money.
Check that the account exists and that its name matches the location's `job` exactly. Restart the banking resource before restarting `jg-mechanic`.
Check that your banking integration can add money to a society account. Confirm that `Framework.Server.PayIntoSocietyFund` calls the correct server export for your banking resource.
# Changelog (/vehicle-studio/changelog)
# v1.0.0 (/vehicle-studio/changelog/v1.0.0)
Initial release.
# v1.0.1 (/vehicle-studio/changelog/v1.0.1)
Due to the number of files changed, we highly recommend a full reinstall of the script to ensure everything works smoothly!
## Fixes [#fixes]
* Fallback images not showing
* Place client into routing bucket while taking images
* Exports can now lookup images via hash (to resolve issues for ESX users)
## Qbox CDN Uploads [#qbox-cdn-uploads]
* Will now set a filename (and use a subfolder if configured) - thanks for adding these features ??
## Config [#config]
* New config option added `Config.VehicleStudioRoutingBuckets`
## Database (if configured) [#database-if-configured]
* If you've set up the script to store metadata in your database, run the following migration SQL:
```sql
ALTER TABLE `jg_vehiclestudio_vehicles`
ADD COLUMN `model_hash` int unsigned NOT NULL DEFAULT 0 AFTER `spawn_code`,
ADD KEY `idx_model_hash` (`model_hash`);
ALTER TABLE `jg_vehiclestudio_images`
ADD COLUMN `path` text NULL AFTER `updated_at`,
ADD COLUMN `storage_key` text NULL AFTER `path`;
```
## API [#api]
* The data returned from the exports have changed slightly. Please ensure you also update your JG Advanced Garages & JG Dealerships to the latest versions released today. You can read more about the available exports for integrations here: [https://docs.jgscripts.com/vehicle-studio/api](https://docs.jgscripts.com/vehicle-studio/api)
## Changed Files [#changed-files]
* LOCAL\_IMAGE\_UPLOADS\_HANDOFF.md
* VEHICLE\_IMAGE\_EXPORT\_HANDOFF.md
* client/cl-gallery-settings.lua
* client/cl-gallery.lua
* client/cl-main.lua
* client/cl-state.lua
* client/cl-studio.lua
* fxmanifest.lua
* server/sv-database.lua
* server/sv-gallery-settings.lua
* server/sv-http.lua
* server/sv-image-index.lua
* server/sv-perms.lua
* server/sv-routing-buckets.lua
* server/sv-upload.lua
* shared/\_types.lua
* web (entire folder)
* config (config files changed)
# v1.0.2 (/vehicle-studio/changelog/v1.0.2)
## Fixes [#fixes]
* Prevent vehicles from floating when bulk processing location-based photos
* Skip when vehicle is registered but model missing
* Detect & retry when WebGL view is black/partially black
* Some other small fixes and reliability improvements
## QoL [#qol]
* Added toggleable logs view to bulk processing UI
* Debug log when image lookup fails via exports
## Changed Files [#changed-files]
* VEHICLE\_IMAGE\_EXPORT\_HANDOFF.md
* client/cl-gallery.lua
* client/cl-studio.lua
* client/cl-vehicle.lua
* server/sv-http.lua
* server/sv-image-index.lua
* server/sv-upload.lua
* web (entire folder)
# v1.1.0 (/vehicle-studio/changelog/v1.1.0)
## What's New [#whats-new]
* New & refined design; utilising a custom library called JG UI
* Added ability to toggle extras
## Fixes [#fixes]
* Fix failing ESX hash lookups
* Fix batch processing freezing on invalid vehicles
* Stability improvements across the codebase (better error handling, timeouts, etc.)
## Locales [#locales]
* Added French locale
## Changed Files [#changed-files]
* client/cl-extras.lua
* client/cl-gallery-settings.lua
* client/cl-gallery.lua
* client/cl-mods.lua
* client/cl-state.lua
* client/cl-studio.lua
* client/cl-vehicle.lua
* server/sv-database.lua
* server/sv-gallery-settings.lua
* server/sv-image-index.lua
* shared/\_types.lua
* web (entire folder)
# AWS S3 (/vehicle-studio/image-uploads/aws-s3)
S3 from Amazon Web Services (AWS) is the most flexible option, but it is also the easiest to misconfigure. Vehicle Studio signs a short-lived S3 PUT URL, then the NUI uploads the image directly to S3.
You need four things to work at the same time:
| Step | What you are setting up | Why it matters |
| ---- | ------------------------- | --------------------------------------------- |
| 1 | Bucket | The place images are stored |
| 2 | IAM access key | Lets the server create presigned upload URLs |
| 3 | CORS | Lets the NUI upload directly from the browser |
| 4 | Public read access or CDN | Lets browsers load the saved image URL |
## The Two URLs To Understand [#the-two-urls-to-understand]
S3 has an upload URL and a public URL.
The upload URL is a short-lived presigned URL. Vehicle Studio creates it on the server, then the NUI uses it once to upload the image directly to S3.
The public URL is the final URL saved for the image. This is what players' browsers and other resources load later. In most setups it looks like one of these:
```txt
https://my-vehicle-images.s3.eu-west-2.amazonaws.com
https://cdn.example.com
```
If you are using CloudFront or another CDN, put the CDN domain in `publicUrl`.
Vehicle Studio appends a small `v=` query parameter to saved image URLs each time an image is retaken. The object path stays the same, but browsers are forced to fetch the newest version.
### Create An S3 Bucket [#create-an-s3-bucket-step]
1. Open the [AWS S3 console](https://s3.console.aws.amazon.com/s3/).
2. Create a bucket, for example `my-vehicle-images`.
3. Pick the AWS region closest to your server, for example `eu-west-2`.
4. Save the bucket name and region.
Bucket names are globally unique, so yours must be different from everyone else's.
### Create An IAM User Or Access Key [#create-an-iam-user-or-access-key-step]
Vehicle Studio needs an access key ID and secret access key that can create presigned PUT URLs for the bucket.
AWS documentation:
* [Create access keys](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html)
* [IAM policies for S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/security-iam.html)
This example allows uploads and reads inside the `vehicle-studio/` folder only:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject"],
"Resource": "arn:aws:s3:::my-vehicle-images/vehicle-studio/*"
}
]
}
```
Replace `my-vehicle-images` with your bucket name. Replace `vehicle-studio/` if you use a different `prefix`.
### Enable CORS For Direct Uploads [#enable-cors-for-direct-uploads-step]
Because the NUI uploads directly to S3, the bucket must allow browser `PUT` requests from your resource.
In the S3 bucket, open **Permissions** > **Cross-origin resource sharing (CORS)** and use this as a starting point:
```json
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["PUT"],
"AllowedOrigins": ["https://cfx-nui-jg-vehiclestudio"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]
```
If you renamed the resource, replace `jg-vehiclestudio` in the origin. For quick testing, you can temporarily use `"*"` as the allowed origin, then tighten it afterwards.
### Make Uploaded Images Public [#make-uploaded-images-public-step]
This step controls whether the final image URL can actually be loaded in a browser.
For a simple public bucket setup, add a bucket policy like this:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadVehicleStudioImages",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-vehicle-images/vehicle-studio/*"
}
]
}
```
If AWS blocks this policy, check the bucket's **Block Public Access** settings. AWS blocks public bucket policies by default in many setups.
If you do not want a public bucket, use CloudFront or another CDN in front of the bucket instead. In that case, set `publicUrl` to the CDN domain.
AWS documentation:
* [Bucket policies](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-policies.html)
* [Block Public Access](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html)
* [CloudFront with S3](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html)
### Configure Vehicle Studio [#configure-vehicle-studio-step]
In `config/config.lua`:
```lua
Config.ImageStorageProvider = "s3"
```
In `config/config.upload.lua`:
```lua
Config.ImageStorageProviders = Config.ImageStorageProviders or {}
Config.ImageStorageProviders.s3 = {
bucket = "my-vehicle-images",
region = "eu-west-2",
accessKeyId = "YOUR_AWS_ACCESS_KEY_ID",
secretAccessKey = "YOUR_AWS_SECRET_ACCESS_KEY",
publicUrl = "https://my-vehicle-images.s3.eu-west-2.amazonaws.com",
prefix = "vehicle-studio",
acl = nil,
endpoint = nil,
forcePathStyle = false,
presignExpires = 900
}
```
If you use CloudFront:
```lua
publicUrl = "https://cdn.example.com"
```
If you use a custom S3-compatible endpoint, set `endpoint`. Normal AWS S3 does not need it.
### Test The Final URL [#test-the-final-url-step]
After restarting the resource, generate one image and open the saved image URL in a browser.
For example:
```txt
https://my-vehicle-images.s3.eu-west-2.amazonaws.com/vehicle-studio/adder.webp
```
If that URL does not open publicly, the issue is usually the bucket policy, Block Public Access settings, CDN setup, or an incorrect `publicUrl`.
## Config Fields [#config-fields]
| Field | Required | Description |
| ----------------- | -------- | -------------------------------------------------------------------------- |
| `bucket` | Yes | S3 bucket name. |
| `region` | Yes | AWS region, for example `"eu-west-2"`. |
| `accessKeyId` | Yes | AWS access key ID. |
| `secretAccessKey` | Yes | AWS secret access key. |
| `publicUrl` | Yes | Public read URL used to build the final saved image URL. |
| `prefix` | No | Folder-style prefix for uploaded images, for example `"vehicle-studio"`. |
| `acl` | No | Optional canned ACL. Leave `nil` unless you know the provider requires it. |
| `endpoint` | No | Optional custom S3-compatible endpoint. Normal AWS S3 does not need this. |
| `forcePathStyle` | No | Optional for S3-compatible providers. |
| `presignExpires` | No | Presigned upload URL lifetime in seconds. Defaults to `900`. |
## Troubleshooting [#troubleshooting]
Check:
* The bucket region.
* The endpoint, if you configured one.
* The access key and secret key.
* The system clock on the server.
* Whether the browser is sending the exact `Content-Type` from the presigned upload plan.
Check `publicUrl`. It must be the public read URL that can load the uploaded image directly in a browser.
Examples:
```lua
publicUrl = "https://my-vehicle-images.s3.eu-west-2.amazonaws.com"
publicUrl = "https://cdn.example.com"
```
Do not include a trailing slash.
Also check the bucket policy, Block Public Access settings, or CDN origin configuration.
# Cloudflare R2 (/vehicle-studio/image-uploads/cloudflare-r2)
Cloudflare R2 is a good hosted option if you want S3-compatible storage without setting up AWS (as well as a pretty awesome free tier).
Vehicle Studio signs a short-lived R2 PUT URL, then the NUI uploads the image directly to R2.
You'll need:
* An R2 bucket.
* An R2 access key ID.
* An R2 secret access key.
* Your Cloudflare account ID.
* A public URL for reading uploaded files.
* CORS enabled for browser PUT uploads.
### Create An R2 Bucket [#create-an-r2-bucket-step]
1. Open the [Cloudflare dashboard](https://dash.cloudflare.com/).
2. Go to **R2 Object Storage**.
3. Create a bucket, for example `vehicle-studio`.
4. Save the bucket name.
### Create R2 API Tokens [#create-r2-api-tokens-step]
1. In the Cloudflare dashboard, open **R2 Object Storage**.
2. Go to **Manage R2 API Tokens**.
3. Create an API token with object read and write access for the bucket.
4. Copy the access key ID and secret access key.
Cloudflare documents this flow here: [R2 API tokens](https://developers.cloudflare.com/r2/api/tokens/).
### Make The Bucket Public [#make-the-bucket-public-step]
Uploaded images must be reachable by the browser and by other scripts that use the saved image URL.
You can use either:
* An R2 public bucket URL.
* A custom domain connected to the R2 bucket.
Cloudflare's public bucket and custom domain options are documented here: [Public buckets](https://developers.cloudflare.com/r2/buckets/public-buckets/).
### Enable CORS For Direct Uploads [#enable-cors-for-direct-uploads-step]
Because the NUI uploads directly to R2, the bucket must allow browser `PUT` requests from your resource.
Use this as a starting point:
```json
[
{
"AllowedOrigins": ["https://cfx-nui-jg-vehiclestudio"],
"AllowedMethods": ["PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]
```
For quick testing, you can temporarily use `"*"` as the allowed origin, then tighten it afterwards.
### Configure Vehicle Studio [#configure-vehicle-studio-step]
In `config/config.lua`:
```lua
Config.ImageStorageProvider = "r2"
```
In `config/config.upload.lua`:
```lua
Config.ImageStorageProviders = Config.ImageStorageProviders or {}
Config.ImageStorageProviders.r2 = {
accountId = "YOUR_CLOUDFLARE_ACCOUNT_ID",
bucket = "vehicle-studio",
accessKeyId = "YOUR_R2_ACCESS_KEY_ID",
secretAccessKey = "YOUR_R2_SECRET_ACCESS_KEY",
publicUrl = "https://pub-xxxxxxxxxxxxxxxx.r2.dev",
prefix = "vehicle-studio",
region = "auto",
forcePathStyle = true,
acl = nil,
presignExpires = 900
}
```
Use your custom domain in `publicUrl` if you configured one (Cloudflare recommends this):
```lua
publicUrl = "https://images.example.com"
```
Cloudflare notes that presigned uploads use the R2 S3 API domain, not a custom domain. `publicUrl` is still the final public URL saved after the upload.
## Config Fields [#config-fields]
| Field | Required | Description |
| ----------------- | -------- | -------------------------------------------------------------------------- |
| `accountId` | Yes | Cloudflare account ID. |
| `bucket` | Yes | R2 bucket name. |
| `accessKeyId` | Yes | R2 access key ID. |
| `secretAccessKey` | Yes | R2 secret access key. |
| `publicUrl` | Yes | Public read URL used to build the final saved image URL. |
| `prefix` | No | Folder-style prefix for uploaded images, for example `"vehicle-studio"`. |
| `region` | No | R2 uses `"auto"`. |
| `forcePathStyle` | No | Required for R2-compatible signing. Defaults should normally stay enabled. |
| `acl` | No | Optional canned ACL. Leave `nil` unless you know the provider requires it. |
| `presignExpires` | No | Presigned upload URL lifetime in seconds. Defaults to `900`. |
## Troubleshooting [#troubleshooting]
Check:
* The Cloudflare account ID.
* The bucket name.
* The access key and secret key.
* The system clock on the server.
* Whether the NUI is sending the exact `Content-Type` from the presigned upload plan.
Check `publicUrl`. It must be the public read URL that can load the uploaded image directly in a browser.
Examples:
```lua
publicUrl = "https://pub-xxxxxxxxxxxxxxxx.r2.dev"
publicUrl = "https://images.example.com"
```
Do not include a trailing slash.
Also check the bucket's public access or custom domain configuration.
# Fivemanage (/vehicle-studio/image-uploads/fivemanage)
Fivemanage is the simplest hosted option because it is designed around FiveM resources. Vehicle Studio asks Fivemanage for a presigned upload URL, then the NUI uploads the image directly to Fivemanage.
### Create A Fivemanage API Token [#create-a-fivemanage-api-token-step]
1. Open the [Fivemanage dashboard](https://fivemanage.com/).
2. Sign in or create an account.
3. Go to your API token or developer settings page.
4. Create a new API token.
5. Copy the token into `config/config.upload.lua`.
The exact dashboard labels may change over time, but you are looking for an API token that allows file uploads.
### Configure Vehicle Studio [#configure-vehicle-studio-step]
In `config/config.lua`:
```lua
Config.ImageStorageProvider = "fivemanage"
```
In `config/config.upload.lua`:
```lua
Config.ImageStorageProviders = Config.ImageStorageProviders or {}
Config.ImageStorageProviders.fivemanage = {
apiKey = "YOUR_FIVEMANAGE_API_TOKEN",
presignedEndpoint = "https://api.fivemanage.com/api/v3/file/presigned-url",
path = "vehicle-studio",
metadata = "",
retentionExempt = false,
presignExpires = 900
}
```
### Test The Upload [#test-the-upload-step]
After restarting the resource, generate a vehicle image. Fivemanage returns the final public URL, and Vehicle Studio saves that URL directly.
If uploads fail, check:
* The API token was copied correctly.
* The token has upload access.
* The `path` value is valid for your Fivemanage account.
* The server can make outbound HTTPS requests to create the presigned URL.
## Config Fields [#config-fields]
| Field | Required | Description |
| ------------------- | -------- | ------------------------------------------------------------------------------------------ |
| `apiKey` | Yes | Fivemanage API token used to create presigned upload URLs. |
| `presignedEndpoint` | No | Defaults to `"https://api.fivemanage.com/api/v3/file/presigned-url"`. |
| `path` | No | Folder path to upload into. Defaults to no folder if omitted. |
| `metadata` | No | Optional metadata JSON string sent with the upload. |
| `retentionExempt` | No | Optional Fivemanage retention setting. |
| `presignExpires` | No | Presigned upload URL lifetime in seconds. Defaults to Fivemanage's API default if omitted. |
## Troubleshooting [#troubleshooting]
Check that `Config.ImageStorageProviders.fivemanage.apiKey` is correct and still active.
Check that:
* `Config.ImageStorageProvider` is set to `"fivemanage"`.
* `config/config.upload.lua` is loaded on the server.
* The server can make outbound HTTPS requests to Fivemanage.
* The Fivemanage API token has not expired or been revoked.
# Image Uploads (/vehicle-studio/image-uploads)
Vehicle Studio creates vehicle images in the in-game browser. Those images need to be stored somewhere before they can be shown in the gallery or used by other scripts.
**For live servers, use a remote upload provider.** The local storage option is mainly for localhost testing or advanced users who have their own HTTPS reverse proxy.
Do not use local storage as your live server setup unless you already know how to run a proper HTTPS reverse proxy. A plain public server IP such as `http://123.123.123.123:30120` will be blocked by the browser.
The main provider switch lives in `config/config.lua`:
```lua
Config.ImageStorageProvider = "qbox"
```
API keys and private provider settings live in `config/config.upload.lua`, which is loaded on the server only. Keep that file private and do not commit it to a public repository.
## Which Option Should I Use? [#which-option-should-i-use]
If your server is live, choose one of these:
```lua
Config.ImageStorageProvider = "qbox"
Config.ImageStorageProvider = "fivemanage"
Config.ImageStorageProvider = "r2"
Config.ImageStorageProvider = "s3"
```
If you are only testing on your own machine, you can use:
```lua
Config.ImageStorageProvider = "local"
```
## Provider Options [#provider-options]
| Provider | Config value | Live server? | Best for | Guide |
| ------------- | -------------- | ----------------------------------- | -------------------------------------------- | ---------------------------------------------------------- |
| Qbox CDN | `"qbox"` | Yes | Simple hosted uploads through Qbox | [Setup guide](/vehicle-studio/image-uploads/qbox-cdn) |
| Fivemanage | `"fivemanage"` | Yes | FiveM-focused hosted image uploads | [Setup guide](/vehicle-studio/image-uploads/fivemanage) |
| Cloudflare R2 | `"r2"` | Yes | Cloudflare object storage and custom domains | [Setup guide](/vehicle-studio/image-uploads/cloudflare-r2) |
| AWS S3 | `"s3"` | Yes | Existing AWS setups and custom CDN pipelines | [Setup guide](/vehicle-studio/image-uploads/aws-s3) |
| Local storage | `"local"` | No, except with HTTPS reverse proxy | Local-only image generation | [Setup guide](/vehicle-studio/image-uploads/local-storage) |
## Setup Order [#setup-order]
1. Pick a provider from the table above.
2. Set `Config.ImageStorageProvider` in `config/config.lua`.
3. Fill in the matching provider block in `config/config.upload.lua` if the provider needs private settings.
4. Restart the resource after changing upload settings.
5. Open Vehicle Studio and take one test photo before running bulk photography.
## Why Local Storage Is Not Recommended For Live Servers [#why-local-storage-is-not-recommended-for-live-servers]
Local storage uses Vehicle Studio's built-in FiveM HTTP endpoint. The in-game browser must upload image files to that endpoint and load gallery images back from it.
On live servers, this usually fails because the Vehicle Studio UI is loaded from an HTTPS page such as:
```txt
https://cfx-nui-jg-vehiclestudio/web/dist/index.html
```
Browsers block that HTTPS page from sending uploads to a plain HTTP public IP such as:
```txt
http://123.123.123.123:30120/jg-vehiclestudio/save
```
That browser rule is called mixed content blocking. It happens before the request reaches Vehicle Studio.
`localhost` is different. Browsers treat localhost as a trusted local address, so local storage can work for development:
```lua
Config.HttpBaseUrl = "http://localhost:30120"
```
The generated Cfx proxy URL, such as `https://something.users.cfx.re`, is also not a good local storage fix. It is rate limited and not designed for large image upload POST requests.
## Remote Batch Upload Queue [#remote-batch-upload-queue]
Batch photography can upload remote images concurrently for efficiency.
The queue settings live in `config/config.upload.lua`:
```lua
Config.RemoteImageUploadQueue = {
enabled = true,
concurrency = 3,
maxPendingUploads = 6,
maxAttempts = 5,
retryBaseDelayMs = 500,
retryMaxDelayMs = 8000,
}
```
`concurrency` controls how many remote uploads can run at the same time. `maxPendingUploads` controls how many captured images can be held by the upload queue before the batch runner pauses and waits for uploads to catch up. `maxAttempts` is the total number of tries for each upload phase, so `1` means no retry.
If you're having issues, set the following to make remote batch uploads happen one at a time.
```lua
Config.RemoteImageUploadQueue = {
enabled = true,
concurrency = 1,
maxPendingUploads = 1,
maxAttempts = 1,
retryBaseDelayMs = 1,
retryMaxDelayMs = 1,
}
```
## Keep Secrets Private [#keep-secrets-private]
Never put real API keys in Git commits, support tickets, Discord screenshots, or client-side Lua files.
Only `config/config.upload.lua` should contain upload credentials.
## Troubleshooting [#troubleshooting]
This usually means you are using `Config.ImageStorageProvider = "local"` and the browser cannot reach the local HTTP endpoint.
For live servers, switch to Qbox CDN, Fivemanage, R2, or S3.
For localhost testing, check the local storage guide.
You are trying to use local storage through a plain HTTP public IP. This is blocked by the browser. Use a remote provider, or use local storage only behind a real HTTPS reverse proxy.
For S3 or R2, the bucket CORS rules are missing or the allowed origin does not match your resource. Check the CORS section in the provider guide.
For local storage, check the local storage guide.
Check `Config.ImageStorageProvider` in `config/config.lua`. It must match one of:
```txt
local
qbox
fivemanage
r2
s3
```
# Local Storage (/vehicle-studio/image-uploads/local-storage)
Local storage saves generated images inside the Vehicle Studio resource folder. It is useful for localhost testing, but it is not the recommended setup for live servers.
If your server is live and players connect from outside your own machine, use Qbox CDN, Fivemanage, Cloudflare R2, or AWS S3 instead. Local storage will not work out of the box on most live servers.
In `config/config.lua`:
```lua
Config.ImageStorageProvider = "local"
```
Images are saved to:
```txt
exported_images/
```
Vehicle Studio then serves those files through its built-in FiveM HTTP endpoint.
## When To Use Local Storage [#when-to-use-local-storage]
Use local storage if:
* You are testing Vehicle Studio on localhost.
* You are developing locally.
* You are an advanced user and already have a proper HTTPS reverse proxy.
Do not use local storage if:
* Your server is live and you want the simplest reliable setup.
* You are trying to use a public IP with `http://`.
* You are trying to use a `users.cfx.re` proxy URL.
* You do not know what an HTTPS reverse proxy is.
For live servers, choose one of these instead:
```lua
Config.ImageStorageProvider = "qbox"
Config.ImageStorageProvider = "fivemanage"
Config.ImageStorageProvider = "r2"
Config.ImageStorageProvider = "s3"
```
## Why Local Storage Fails On Live Servers [#why-local-storage-fails-on-live-servers]
Vehicle Studio runs inside the in-game browser. That browser loads the UI from an HTTPS page, for example:
```txt
https://cfx-nui-jg-vehiclestudio/web/dist/index.html
```
When local storage is enabled, the browser must upload image files to the Vehicle Studio HTTP endpoint, for example:
```txt
http://123.123.123.123:30120/jg-vehiclestudio/save
```
Browsers block HTTPS pages from sending requests to plain HTTP public IPs. This is called mixed content blocking.
That means this is not supported for live servers:
```lua
Config.HttpBaseUrl = "http://123.123.123.123:30120"
```
`localhost` is treated differently by browsers, which is why this can work for local testing:
```lua
Config.HttpBaseUrl = "http://localhost:30120"
```
## Basic Localhost Setup [#basic-localhost-setup]
For most local development setups, leave `Config.HttpBaseUrl` empty:
```lua
Config.ImageStorageProvider = "local"
Config.HttpBaseUrl = nil
```
Vehicle Studio will try to detect the local endpoint automatically.
If automatic detection fails while testing locally, set:
```lua
Config.HttpBaseUrl = "http://localhost:30120"
```
Replace `30120` if your server uses a different port.
## Do Not Use The Cfx Proxy [#do-not-use-the-cfx-proxy]
Do not set `Config.HttpBaseUrl` to a generated Cfx proxy URL:
```lua
Config.HttpBaseUrl = "https://something.users.cfx.re"
```
That URL may look useful because it uses HTTPS, but it is not suitable for image uploads. The Cfx proxy is rate limited and is not designed for large POST upload traffic.
## Advanced Live Server Setup [#advanced-live-server-setup]
Only use local storage on a live server if you can provide your own HTTPS reverse proxy. There is an extensive guide in the Cfx.re documentation on how to do this:
Example:
```lua
Config.ImageStorageProvider = "local"
Config.HttpBaseUrl = "https://images.example.com"
```
Do not include the resource name in `Config.HttpBaseUrl`. Vehicle Studio adds it automatically.
So this is correct:
```lua
Config.HttpBaseUrl = "https://images.example.com"
```
This is not correct:
```lua
Config.HttpBaseUrl = "https://images.example.com/jg-vehiclestudio"
```
Your proxy must forward requests like these to the FiveM resource HTTP API:
```txt
https://images.example.com/jg-vehiclestudio/health
https://images.example.com/jg-vehiclestudio/save
https://images.example.com/jg-vehiclestudio/image/adorned.webp
```
The proxy must:
* Use HTTPS with a valid certificate.
* Allow `GET`, `POST`, `DELETE`, and `OPTIONS`.
* Allow large enough POST bodies for image uploads.
* Avoid strict rate limits that would break batch photography.
* Forward headers such as `Content-Type`, `X-Vehicle-Model`, `X-Image-Format`, `X-Image-Id`, and `X-Preset-Id`.
* Return valid CORS headers.
If this list does not make sense, do not use local storage on a live server. Use a remote provider instead.
## Troubleshooting [#troubleshooting]
**Vehicle Studio Says It Cannot Connect To The Server**
This comes from the local HTTP endpoint health check.
If you are on a live server, the simplest fix is to stop using local storage and switch to Qbox CDN, Fivemanage, R2, or S3.
If you are testing locally, check:
* `Config.ImageStorageProvider` is set to `"local"`.
* `Config.HttpBaseUrl` is empty or points to `http://localhost:30120`.
* The port matches your FiveM server port.
* The resource was restarted after changing config.
You are using a plain HTTP public IP. The browser blocks this before Vehicle Studio receives the request.
Use a remote provider or put local storage behind a real HTTPS reverse proxy.
For local storage, this usually means `Config.HttpBaseUrl` points to the wrong place or your reverse proxy is not handling CORS correctly.
Check that:
* The URL does not include `/jg-vehiclestudio`.
* The URL is reachable from the game client.
* The proxy allows upload POST requests.
* The proxy returns valid CORS headers.
Check that:
* Files are being written to `exported_images/`.
* The configured URL can be reached from the game client.
* The server firewall allows access to the selected port or proxy.
* Any reverse proxy forwards requests to the FiveM HTTP server.
# Qbox CDN (/vehicle-studio/image-uploads/qbox-cdn)
Qbox CDN is a hosted CDN option for FiveM media. Vehicle Studio asks Qbox for a presigned upload URL, then the NUI uploads the image directly to Qbox without exposing your API key in the browser.
### Create A Qbox CDN API Token [#create-a-qbox-cdn-api-token-step]
1. Open the [Qbox dashboard](https://dashboard.qbox.re/).
2. Sign in with Discord.
3. Go to the CDN area.
4. Generate a new API token.
5. Copy the token into `config/config.upload.lua`.
The exact dashboard labels may change over time, but you are looking for a CDN API token that can upload files. If Qbox shows token permissions, make sure uploads are allowed. If you want Vehicle Studio to delete remote images when you delete gallery entries, make sure deletes are allowed too.
### Configure Vehicle Studio [#configure-vehicle-studio-step]
In `config/config.lua`:
```lua
Config.ImageStorageProvider = "qbox"
```
In `config/config.upload.lua`:
```lua
Config.ImageStorageProviders = Config.ImageStorageProviders or {}
Config.ImageStorageProviders.qbox = {
apiKey = "YOUR_QBOX_CDN_API_TOKEN",
presignedEndpoint = "https://api.qbox.re/v1/file/presigned-url",
deleteEndpoint = "https://api.qbox.re/v1/file",
presignExpires = 300
}
```
Qbox accepts either a raw API key or a `Bearer ...` value in `apiKey`. Use the exact value Qbox gives you unless their dashboard tells you to include the `Bearer` prefix.
### Test The Upload [#test-the-upload-step]
After restarting the resource, generate a vehicle image. Qbox returns the final public URL, and Vehicle Studio saves that URL directly.
If uploads fail, check:
* The API token was copied correctly.
* The token has upload access.
* Your Qbox CDN storage limit has not been reached.
* The server can make outbound HTTPS requests to Qbox.
* The NUI can make outbound HTTPS requests to the presigned Qbox upload URL.
## Config Fields [#config-fields]
| Field | Required | Description |
| ------------------- | -------- | ---------------------------------------------------------------------------------------- |
| `apiKey` | Yes | Qbox CDN API token used to create presigned upload URLs. |
| `presignedEndpoint` | No | Defaults to `"https://api.qbox.re/v1/file/presigned-url"`. |
| `deleteEndpoint` | No | Defaults to `"https://api.qbox.re/v1/file"`. Used when deleting gallery images. |
| `presignExpires` | No | Local pending-upload lifetime in seconds. Qbox currently returns 300 seconds by default. |
## Troubleshooting [#troubleshooting]
Check that `Config.ImageStorageProviders.qbox.apiKey` is correct and still active. If you pasted only the token value and Qbox requires a bearer value for your token, set it as:
```lua
apiKey = "Bearer YOUR_QBOX_CDN_API_TOKEN"
```
Check that:
* `Config.ImageStorageProvider` is set to `"qbox"`.
* `config/config.upload.lua` is loaded on the server.
* The server can make outbound HTTPS requests to `https://api.qbox.re`.
* The Qbox CDN API token has not expired or been revoked.
Vehicle Studio deletes Qbox files through the Qbox API. Check that:
* Your Qbox API token allows deletes.
* `deleteEndpoint` is still set to `https://api.qbox.re/v1/file`, unless Qbox has given you a different endpoint.
* The saved image URL is a Qbox CDN URL so Vehicle Studio can resolve the storage path.
# Client Exports (/vehicle-mileage/exports/client-exports)
## getMileage [#getmileage]
Returns the mileage of the vehicle you are currently in.
```lua
-- client only
local mileageKm = exports["jg-vehiclemileage"]:getMileage()
```
| Returns | Type | Description |
| ----------- | --------------- | ------------------------------------------------------------- |
| `mileageKm` | `number\|false` | Mileage in kilometres, or `false` if the entity doesn't exist |
## getMileageByEntity [#getmileagebyentity]
Returns the mileage of a specified vehicle entity.
```lua
-- client only
-- vehicle: integer - the vehicle entity handle
local mileageKm = exports["jg-vehiclemileage"]:getMileageByEntity(vehicle)
```
| Returns | Type | Description |
| ----------- | --------------- | ------------------------------------------------------------- |
| `mileageKm` | `number\|false` | Mileage in kilometres, or `false` if the entity doesn't exist |
## getMileageByPlate [#getmileagebyplate]
Returns the mileage of a specified vehicle plate.
This export makes a database call, so use sparingly.
```lua
-- client only
-- plate: string
local mileageKm = exports["jg-vehiclemileage"]:getMileageByPlate(plate)
```
| Returns | Type | Description |
| ----------- | --------------- | -------------------------------------------------------------------- |
| `mileageKm` | `number\|false` | Mileage in kilometres, or `false` if the plate isn't in the database |
## getUnit [#getunit]
Returns the unit set in the `jg-vehiclemileage` config.
```lua
-- client only
local unit = exports["jg-vehiclemileage"]:getUnit()
```
| Returns | Type | Description |
| ------- | ----------------------- | ------------------------------- |
| `unit` | `"miles"\|"kilometers"` | The configured measurement unit |
# Exports (/vehicle-mileage/exports)
# Server Exports (/vehicle-mileage/exports/server-exports)
## getMileageByEntity [#getmileagebyentity]
Returns the mileage of a specified vehicle entity.
```lua
-- server only
-- vehicle: integer - the vehicle entity handle
local mileageKm = exports["jg-vehiclemileage"]:getMileageByEntity(vehicle)
```
| Returns | Type | Description |
| ----------- | --------------- | ------------------------------------------------------------- |
| `mileageKm` | `number\|false` | Mileage in kilometres, or `false` if the entity doesn't exist |
## getMileageByPlate [#getmileagebyplate]
Returns the mileage of a specified vehicle plate.
This export makes a database call, so use sparingly.
```lua
-- server only
-- plate: string
local mileageKm = exports["jg-vehiclemileage"]:getMileageByPlate(plate)
```
| Returns | Type | Description |
| ----------- | --------------- | -------------------------------------------------------------------- |
| `mileageKm` | `number\|false` | Mileage in kilometres, or `false` if the plate isn't in the database |
## getUnit [#getunit]
Returns the unit set in the `jg-vehiclemileage` config.
```lua
-- server only
local unit = exports["jg-vehiclemileage"]:getUnit()
```
| Returns | Type | Description |
| ------- | ----------------------- | ------------------------------- |
| `unit` | `"miles"\|"kilometers"` | The configured measurement unit |
# Vehicle Labels (/advanced-garages/guides/esx-guides/vehicle-labels)
If you've added addon vehicles/imports to your server, you may see `NULL` or strange capitalised vehicle labels in the garage interface. ESX doesn't come with a built-in "shared" file to index all vehicle models like QBCore, so this is built into the script instead.
To add a pretty vehicle label to your addon/import vehicles, open `config.lua` and find the `Config.VehicleLabels` section at the bottom of the file. There are two example rows, which you can delete if you're not using those vehicles.
For example, if you've added an RS6 with the spawn code `rs6` and want to add a label for it:
```lua
Config.VehicleLabels = {
["rs6"] = "Audi RS6"
}
```
# Liveries not saving (/advanced-garages/guides/qbcore-guides/liveries-not-saving)
This is a fix for Benny's (`qb-customs`), not JG Advanced Garages. Take your existing vehicles to Benny's and re-apply the livery for the fix to take effect.
Fix this by making a small modification to `qb-core`:
1. Go to `[qb]/qb-core/client/functions.lua`
2. Search (by pressing CTRL+F) for the code below and remove it:
```lua
and GetVehicleLivery(vehicle) ~= 0
```
The function you modified should now look like this:
```lua
local modLivery = GetVehicleMod(vehicle, 48)
if GetVehicleMod(vehicle, 48) == -1 then
modLivery = GetVehicleLivery(vehicle)
end
```
# Why isn't /admincar working?! (/advanced-garages/guides/qbcore-guides/why-isnt-admincar-working)
`/admincar` is a commonly used command in QBCore that lets admins add a spawned-in vehicle to their owned vehicles. What's even more common though is this error:
A few mistakes can stop you owning vehicles in QBCore. This guide explains what might be wrong and how to fix it.
## Issues with the vehicles meta file [#issues-with-the-vehicles-meta-file]
First, have a look at the vehicle itself. Browse to your vehicle's files and go to the "Data" section (this is where the vehicle's .meta files are located). Each vehicle has the following .meta files:
* `carvariations.meta`
* `handling.meta`
* `vehicles.meta`
Some vehicles may have more, but these are the main ones you need to look at.
Typical meta files found with an addon vehicle
First, some fundamentals. Your car's model name is the name of the model in the "Stream" section of your vehicle's files. This is also the vehicle's in-game spawn code once it has been added. You can change the name, but we don't recommend it, as it needs changing in all "Data" files to work properly.
**Model names that don't follow these rules break `/admincar`:**
* NEVER put any capital letters in this name.
* NEVER put any symbols in this name.
* NEVER make the name longer than **12 characters** - this can be unreliable.
`carvariations.meta`: the only thing you should double-check or change is the "modelName".
```xml
pgt3
```
`handling.meta`: the only thing you should double-check or change is the "handlingName".
```xml
pgt3
```
`vehicles.meta`: you'll need to double-check a lot of things here. Below is an example of what to look out for or change.
```xml
pgt3
pgt3
pgt3
pgt3
gt3flat6
VC_SUPER
```
If you've followed these steps, your vehicle model itself should be working. **Restart the server fully and try `/admincar` again**.
You could restart only the resource holding the addon vehicle, but this crashes the game if **ANY** vehicle in the resource is still out in the world, so we don't recommend it.
If you still get the pop-up saying "You can't store this vehicle in your garage..", move on to the next step.
## Mistakes QB-Core/shared/vehicles.lua file [#mistakes-qb-coresharedvehicleslua-file]
The second thing to check is the shared vehicles file, located at `[qb]/qb-core/shared/vehicles.lua`. This should contain every vehicle that can be stored or owned in the garage system. If the vehicle you're trying to `/admincar` isn't in this file, or isn't formatted correctly, it won't work.
The fundamentals from the first part still apply here, so if you're unsure, go back and check those before you continue.
Below is an example of a properly formatted shared vehicle:
```lua
['pgt3'] = {
['name'] = '911 GT3 RS',
['brand'] = 'Porsche',
['model'] = 'pgt3',
['price'] = 195000,
['category'] = 'super',
['hash'] = `pgt3`,
['shop'] = 'pdm',
},
```
Here's what to put where:
`['pgt3'] = {` This should be set to the model name. (check fundamentals to see what is not allowed and where to find this.)
`['name'] = '911 GT3 RS',` This should be set to the vehicle's name, which is displayed in the garage system. Capital letters and symbols are allowed here.
`['brand'] = 'Porsche',` This should be set to the vehicle's brand, which is displayed in the garage system. Capital letters and symbols are allowed here.
`['model'] = 'pgt3',` This should be set to the model name. (check fundamentals to see what is not allowed and where to find this.)
`['price'] = 195000,` This sets the price for QB-Vehicleshop and should reflect how much the vehicle is worth. Some replacement vehicle shops also use it to set the price, or to import vehicles into their store using the data in the `shared/vehicles.lua` file.
`['category'] = 'super',` This should be set to the vehicle category. Some scripts use it to determine how much inventory capacity the vehicle has, or which store category it gets added to.
`['hash'] = pgt3,` This should be set to the model name. (check fundamentals to see what is not allowed and where to find this.)
`['shop'] = 'pdm',` This is set to pdm by default. QB-Vehicleshop and most other replacements support multiple dealerships, so set this to the dealership you want this vehicle sold in. Replacement shops that import vehicles from `shared/vehicles.lua` also use this field.