├── web
├── sound.mp3
├── script.js
└── index.html
├── stream
└── vehicle_paint_ramps.ytd
├── client
├── utils
│ ├── enums
│ │ ├── WheelType.lua
│ │ └── VehicleClass.lua
│ ├── getModLabel.lua
│ └── installMod.lua
├── options
│ ├── windowtint.lua
│ ├── plateindex.lua
│ ├── interior.lua
│ ├── dashboard.lua
│ ├── wheelcolor.lua
│ ├── tyresmoke.lua
│ ├── xenon.lua
│ ├── pearlescent.lua
│ └── livery.lua
├── menus
│ ├── extras.lua
│ ├── paint.lua
│ ├── colors.lua
│ ├── neon.lua
│ ├── parts.lua
│ ├── performance.lua
│ ├── main.lua
│ └── wheels.lua
├── zones.lua
└── dragcam.lua
├── types.lua
├── .github
└── FUNDING.yml
├── fxmanifest.lua
├── LICENSE
├── carmodcols_gen9.meta
├── README.md
├── server.lua
├── carcols_gen9.meta
└── config.lua
/web/sound.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberttheprince/popcornrp-customs/HEAD/web/sound.mp3
--------------------------------------------------------------------------------
/stream/vehicle_paint_ramps.ytd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberttheprince/popcornrp-customs/HEAD/stream/vehicle_paint_ramps.ytd
--------------------------------------------------------------------------------
/web/script.js:
--------------------------------------------------------------------------------
1 | const audioPlayer = new Howl({ src: ["sound.mp3"] });
2 |
3 | window.addEventListener("message", (event) => {
4 | if (event.data.sound) {
5 | audioPlayer.play();
6 | }
7 | });
8 |
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/client/utils/enums/WheelType.lua:
--------------------------------------------------------------------------------
1 | ---@enum WheelType
2 | return {
3 | Sport = 0,
4 | Muscle = 1,
5 | Lowrider = 2,
6 | SUV = 3,
7 | Offroad = 4,
8 | Tuner = 5,
9 | Bike = 6,
10 | HighEnd = 7,
11 | BennysOriginal = 8,
12 | BennysBespoke = 9,
13 | OpenWheel = 10,
14 | Street = 11,
15 | Track = 12,
16 | }
--------------------------------------------------------------------------------
/types.lua:
--------------------------------------------------------------------------------
1 | ---@meta
2 | ---@class ZoneOptions
3 | ---@field freeRepair string[]? -- Array of jobs that can repair vehicles for free
4 | ---@field freeMods string[]? -- Array of jobs that can modify vehicles for free
5 | ---@field job string[]? -- Array of jobs that can access the zone
6 | ---@field hideBlip boolean? -- Hide the blip on the map
7 | ---@field blipLabel string? -- Name for the blip on the map
8 | ---@field blipColor number? -- Color for the blip on the map
9 | ---@field points vector3[] -- Array of points that make up the zone
--------------------------------------------------------------------------------
/client/utils/getModLabel.lua:
--------------------------------------------------------------------------------
1 | ---@param vehicle number
2 | ---@param modType number
3 | ---@param modValue number
4 | ---@return string
5 | return function (vehicle, modType, modValue)
6 | if Config.ModLabels[modType] then
7 | for _, mod in ipairs(Config.ModLabels[modType]) do
8 | if mod.id == modValue then return mod.label end
9 | end
10 | end
11 |
12 | if modValue == -1 then return 'Stock' end
13 |
14 | local label = GetModTextLabel(vehicle, modType, modValue)
15 | return (not label or label == '') and tostring(modValue) or GetLabelText(label)
16 | end
--------------------------------------------------------------------------------
/client/utils/enums/VehicleClass.lua:
--------------------------------------------------------------------------------
1 | ---@enum VehicleClass
2 | return {
3 | Compacts = 0,
4 | Sedans = 1,
5 | SUVs = 2,
6 | Coupes = 3,
7 | Muscle = 4,
8 | SportsClassics = 5,
9 | Sports = 6,
10 | Super = 7,
11 | Motorcycles = 8,
12 | OffRoad = 9,
13 | Industrial = 10,
14 | Utility = 11,
15 | Vans = 12,
16 | Cycles = 13,
17 | Boats = 14,
18 | Helicopters = 15,
19 | Planes = 16,
20 | Service = 17,
21 | Emergency = 18,
22 | Military = 19,
23 | Commercial = 20,
24 | Trains = 21,
25 | OpenWheels = 22,
26 | }
27 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: popcornrp
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/fxmanifest.lua:
--------------------------------------------------------------------------------
1 | fx_version 'cerulean'
2 | game 'gta5'
3 | lua54 'yes'
4 | use_experimental_fxv2_oal 'yes'
5 |
6 | author 'Jorn and Popcorn Roleplay'
7 | name 'popcornrp-customs'
8 | description 'Customs script using ox_lib'
9 | repository 'https://github.com/alberttheprince/popcornrp-customs'
10 | version '1.4.3'
11 |
12 | ui_page 'web/index.html'
13 |
14 | server_scripts {
15 | 'server.lua',
16 | '@oxmysql/lib/MySQL.lua'
17 | }
18 |
19 | client_scripts {
20 | 'client/menus/main.lua',
21 | 'client/zones.lua',
22 | }
23 | shared_scripts {
24 | '@ox_lib/init.lua',
25 | 'config.lua'
26 | }
27 |
28 | files {
29 | 'client/**/*.lua',
30 | 'web/**/*',
31 | 'carcols_gen9.meta',
32 | 'carmodcols_gen9.meta',
33 | }
34 |
35 | data_file 'CARCOLS_GEN9_FILE' 'carcols_gen9.meta'
36 | data_file 'CARMODCOLS_GEN9_FILE' 'carmodcols_gen9.meta'
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/client/options/windowtint.lua:
--------------------------------------------------------------------------------
1 | local originalWindowTint
2 | local function windowTint()
3 | originalWindowTint = GetVehicleWindowTint(vehicle)
4 |
5 | local windowTintLabels = {}
6 | for i, v in ipairs(Config.WindowTints) do
7 | windowTintLabels[i] = v.label
8 | end
9 |
10 | local option = {
11 | id = 'window_tint',
12 | label = 'Window Tint',
13 | description = ('%s%s'):format(Config.Currency, Config.Prices['colors']),
14 | close = true,
15 | values = windowTintLabels,
16 | set = function(index)
17 | SetVehicleWindowTint(vehicle, index - 1)
18 | return originalWindowTint == index - 1, ('%s windows installed'):format(windowTintLabels[index])
19 | end,
20 | restore = function()
21 | SetVehicleWindowTint(vehicle, originalWindowTint)
22 | end,
23 | defaultIndex = originalWindowTint + 1,
24 | }
25 |
26 | return option
27 | end
28 |
29 | return windowTint
--------------------------------------------------------------------------------
/client/options/plateindex.lua:
--------------------------------------------------------------------------------
1 | local originalPlateIndex
2 | local function plateIndex()
3 | originalPlateIndex = GetVehicleNumberPlateTextIndex(vehicle)
4 |
5 | local plateIndexLabels = {}
6 | for i, v in ipairs(Config.PlateIndexes) do
7 | plateIndexLabels[i] = v.label
8 | end
9 |
10 | local option = {
11 | id = 'plate_index',
12 | label = 'Plate Index',
13 | description = ('%s%s'):format(Config.Currency, Config.Prices['cosmetic']),
14 | close = true,
15 | values = plateIndexLabels,
16 | set = function(index)
17 | SetVehicleNumberPlateTextIndex(vehicle, index - 1)
18 | return originalPlateIndex == index - 1, ('%s plate installed'):format(plateIndexLabels[index])
19 | end,
20 | restore = function()
21 | SetVehicleNumberPlateTextIndex(vehicle, originalPlateIndex)
22 | end,
23 | defaultIndex = originalPlateIndex + 1,
24 | }
25 |
26 | return option
27 | end
28 |
29 | return plateIndex
--------------------------------------------------------------------------------
/client/options/interior.lua:
--------------------------------------------------------------------------------
1 | local originalInterior
2 |
3 | local function interior()
4 | originalInterior = GetVehicleInteriorColor(vehicle)
5 |
6 | local interiorLabels = {}
7 | local interiorIds = {}
8 | local defaultIndex = 0
9 |
10 | for i, v in ipairs(Config.Paints.Classic) do
11 | interiorLabels[i] = v.label
12 | interiorIds[i] = v.id
13 | if v.id == originalInterior then
14 | defaultIndex = i
15 | end
16 | end
17 |
18 | local option = {
19 | id = 'interior',
20 | label = 'Interior',
21 | description = ('%s%s'):format(Config.Currency, Config.Prices['colors']),
22 | close = true,
23 | values = interiorLabels,
24 | set = function(index)
25 | SetVehicleInteriorColor(vehicle, interiorIds[index])
26 | end,
27 | restore = function()
28 | SetVehicleInteriorColor(vehicle, originalInterior)
29 | end,
30 | defaultIndex = defaultIndex,
31 | }
32 |
33 |
34 | return option
35 | end
36 |
37 | return interior
38 |
--------------------------------------------------------------------------------
/client/options/dashboard.lua:
--------------------------------------------------------------------------------
1 | local originalDashboard
2 |
3 | local function dashboard()
4 | originalDashboard = GetVehicleDashboardColor(vehicle)
5 |
6 | local dashboardLabels = {}
7 | local dashboardIds = {}
8 | local defaultIndex = 0
9 |
10 | for i, v in ipairs(Config.Paints.Classic) do
11 | dashboardLabels[i] = v.label
12 | dashboardIds[i] = v.id
13 | if v.id == originalDashboard then
14 | defaultIndex = i
15 | end
16 | end
17 |
18 | local option = {
19 | id = 'dashboard',
20 | label = 'Dashboard',
21 | description = ('%s%s'):format(Config.Currency, Config.Prices['colors']),
22 | close = true,
23 | values = dashboardLabels,
24 | set = function(index)
25 | SetVehicleDashboardColor(vehicle, dashboardIds[index])
26 | end,
27 | restore = function()
28 | SetVehicleDashboardColor(vehicle, originalDashboard)
29 | end,
30 | defaultIndex = defaultIndex,
31 | firstPerson = true
32 | }
33 |
34 |
35 | return option
36 | end
37 |
38 | return dashboard
39 |
--------------------------------------------------------------------------------
/client/options/wheelcolor.lua:
--------------------------------------------------------------------------------
1 | local originalPearlescent, originalWheelColor
2 |
3 | local function wheelcolor()
4 | originalPearlescent, originalWheelColor = GetVehicleExtraColours(vehicle)
5 | local defaultIndex = 1
6 | local ids = {}
7 | local labels = {}
8 |
9 | for i, color in ipairs(Config.Paints.Classic) do
10 | ids[i] = color.id
11 | labels[i] = color.label
12 | if color.id == originalWheelColor then
13 | defaultIndex = i
14 | end
15 | end
16 |
17 | local option = {
18 | id = 'wheelcolor',
19 | label = 'Wheel color',
20 | description = ('%s%s'):format(Config.Currency, Config.Prices['colors']),
21 | ids = ids,
22 | values = labels,
23 | close = true,
24 | set = function(index)
25 | SetVehicleExtraColours(vehicle, originalPearlescent, ids[index])
26 | return originalWheelColor == ids[index], ('%s applied'):format(labels[index])
27 | end,
28 | restore = function()
29 | SetVehicleExtraColours(vehicle, originalPearlescent, originalWheelColor)
30 | end,
31 | defaultIndex = defaultIndex,
32 | }
33 |
34 | return option
35 | end
36 |
37 | return wheelcolor
38 |
--------------------------------------------------------------------------------
/client/utils/installMod.lua:
--------------------------------------------------------------------------------
1 | ---@param duplicate boolean
2 | ---@param mod 'repair' | 'cosmetic' | 'colors' | 11 | 12 | 13 | 15 | 18
3 | ---@param props NotifyProps?
4 | ---@param level number?
5 | return function(duplicate, mod, props, level)
6 | if duplicate then
7 | lib.notify({
8 | title = 'Customs',
9 | description = 'You already have this mod installed',
10 | position = 'top',
11 | type = 'error',
12 | })
13 | return false
14 | end
15 |
16 | local success = lib.callback.await('customs:server:pay', false, mod, level)
17 | if success then
18 | lib.notify({
19 | id = props?.id,
20 | title = props?.title or 'Customs',
21 | description = props?.description,
22 | duration = props?.duration,
23 | position = props?.position or 'top',
24 | type = props?.type or 'success',
25 | style = props?.style,
26 | icon = props?.icon or 'fa-solid fa-wrench',
27 | iconColor = props?.iconColor,
28 | })
29 | SendNUIMessage({sound = true})
30 | return true
31 | end
32 |
33 | lib.notify({
34 | title = 'Customs',
35 | description = 'Not enough money',
36 | position = 'top',
37 | type = 'error',
38 | })
39 | return false
40 | end
--------------------------------------------------------------------------------
/client/options/tyresmoke.lua:
--------------------------------------------------------------------------------
1 | local originalLabelIndex = 1
2 |
3 | local function tyresmoke()
4 | ToggleVehicleMod(vehicle, 20, true)
5 | local r, g, b = GetVehicleTyreSmokeColor(vehicle)
6 |
7 | local rgbValues = {}
8 | local smokeLabels = {}
9 | for i, v in ipairs(Config.TyreSmoke) do
10 | smokeLabels[i] = v.label
11 | rgbValues[i] = {r = v.r, g = v.g, b = v.b}
12 | if v.r == r and v.g == g and v.b == b then
13 | originalLabelIndex = i
14 | end
15 | end
16 |
17 | local option = {
18 | id = 'tyre_smoke',
19 | label = 'Tyre smoke',
20 | description = ('%s%s'):format(Config.Currency, Config.Prices['colors']),
21 | close = true,
22 | values = smokeLabels,
23 | rgbValues = rgbValues,
24 | set = function(index)
25 | local rgb = Config.TyreSmoke[index]
26 | SetVehicleTyreSmokeColor(vehicle, rgb.r, rgb.g, rgb.b)
27 | return originalLabelIndex == index, ('%s installed'):format(Config.TyreSmoke[index].label)
28 | end,
29 | restore = function()
30 | local rgb = Config.TyreSmoke[originalLabelIndex]
31 | SetVehicleTyreSmokeColor(vehicle, rgb.r, rgb.g, rgb.b)
32 | end,
33 | defaultIndex = originalLabelIndex,
34 | }
35 |
36 | return option
37 | end
38 |
39 | return tyresmoke
40 |
--------------------------------------------------------------------------------
/client/options/xenon.lua:
--------------------------------------------------------------------------------
1 | local originalXenon
2 | local originalToggle
3 |
4 | local function xenon()
5 | originalToggle = IsToggleModOn(vehicle, 22)
6 | originalXenon = GetVehicleXenonLightsColor(vehicle)
7 | originalXenon = originalXenon == 255 and -1 or originalXenon
8 |
9 | local xenonLabels = {'Disabled'}
10 | for i, v in ipairs(Config.Xenon) do
11 | xenonLabels[i + 1] = v.label
12 | end
13 |
14 | local option = {
15 | id = 'xenon',
16 | label = 'Xenon',
17 | description = ('%s%s'):format(Config.Currency, Config.Prices['colors']),
18 | close = true,
19 | values = xenonLabels,
20 | set = function(index)
21 | if index == 1 then
22 | ToggleVehicleMod(vehicle, 22, false)
23 | return 'Disabled'
24 | end
25 | ToggleVehicleMod(vehicle, 22, true)
26 | SetVehicleXenonLightsColor(vehicle, index - 3)
27 | return originalXenon == index - 3, ('%s xenon installed'):format(xenonLabels[index])
28 | end,
29 | restore = function()
30 | ToggleVehicleMod(vehicle, 22, originalToggle)
31 | SetVehicleXenonLightsColor(vehicle, originalXenon)
32 | end,
33 | defaultIndex = not originalToggle and 1 or originalXenon + 3,
34 | }
35 |
36 | return option
37 | end
38 |
39 | return xenon
--------------------------------------------------------------------------------
/client/options/pearlescent.lua:
--------------------------------------------------------------------------------
1 | local originalPearlescent, originalWheelColor
2 |
3 | local function pearlescent()
4 | originalPearlescent, originalWheelColor = GetVehicleExtraColours(vehicle)
5 | local defaultIndex = 1
6 | local ids = {}
7 | local labels = {}
8 |
9 | for i, colour in ipairs(Config.Paints.Classic) do
10 | ids[i] = colour.id
11 | labels[i] = colour.label
12 | if colour.id == originalPearlescent then
13 | defaultIndex = i
14 | end
15 | end
16 |
17 | local size = #ids
18 | for i, colour in ipairs(Config.Paints.Chameleon) do
19 | ids[size + i] = colour.id
20 | labels[size + i] = colour.label
21 | if colour.id == originalPearlescent then
22 | defaultIndex = size + i
23 | end
24 | end
25 |
26 | local option = {
27 | id = 'pearlescent',
28 | label = 'Pearlescent',
29 | description = ('%s%s'):format(Config.Currency, Config.Prices['colors']),
30 | ids = ids,
31 | values = labels,
32 | close = true,
33 | set = function(index)
34 | SetVehicleExtraColours(vehicle, ids[index], originalWheelColor)
35 | return originalPearlescent == ids[index], ('%s applied'):format(labels[index])
36 | end,
37 | restore = function()
38 | SetVehicleExtraColours(vehicle, originalPearlescent, originalWheelColor)
39 | end,
40 | defaultIndex = defaultIndex,
41 | }
42 |
43 | return option
44 | end
45 |
46 | return pearlescent
47 |
--------------------------------------------------------------------------------
/client/options/livery.lua:
--------------------------------------------------------------------------------
1 | local originalLivery = {}
2 |
3 | local function livery()
4 | local oldLiveryMethod = GetVehicleLivery(vehicle)
5 | local newLiveryMethod = GetVehicleMod(vehicle, 48)
6 |
7 | if newLiveryMethod >= 0 or oldLiveryMethod == -1 then
8 | originalLivery = {
9 | index = newLiveryMethod,
10 | old = false
11 | }
12 | else
13 | originalLivery = {
14 | index = oldLiveryMethod,
15 | old = true
16 | }
17 | end
18 |
19 | local liveryLabels = {}
20 | if originalLivery.old then
21 | local liveryCount = GetVehicleLiveryCount(vehicle) - 1
22 | for i = 0, liveryCount do
23 | liveryLabels[i + 1] = ('Livery %d'):format(i + 1)
24 | end
25 | else
26 | liveryLabels[1] = 'Stock'
27 | local liveryCount = GetNumVehicleMods(vehicle, 48) - 1
28 | for i = 0, liveryCount do
29 | liveryLabels[i + 2] = ('%s'):format(GetLabelText(GetModTextLabel(vehicle, 48, i)))
30 | end
31 | end
32 |
33 | local option = {
34 | id = 'livery',
35 | label = 'Livery',
36 | description = ('%s%s'):format(Config.Currency, Config.Prices['colors']),
37 | close = true,
38 | values = liveryLabels,
39 | set = function(index)
40 | if originalLivery.old then
41 | SetVehicleLivery(vehicle, index - 1)
42 | return originalLivery.index == index - 1, ('%s installed'):format(liveryLabels[index])
43 | else
44 | SetVehicleMod(vehicle, 48, index - 2, false)
45 | return originalLivery.index == index - 2, ('%s installed'):format(liveryLabels[index])
46 | end
47 | end,
48 | restore = function()
49 | if originalLivery.old then
50 | SetVehicleLivery(vehicle, originalLivery.index)
51 | else
52 | SetVehicleMod(vehicle, 48, originalLivery.index, false)
53 | end
54 | end,
55 | defaultIndex = originalLivery.old and originalLivery.index + 1 or originalLivery.index + 2,
56 | }
57 |
58 |
59 | return option
60 | end
61 |
62 | return livery
63 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Spite License
2 |
3 | Copyright (c) 2023 Noor Nahas
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
11 | EXCEPTIONALLY FROM THE ABOVE LICENSE: THE FOLLOWING LIST OF INDIVIDUALS OR ORGANIZATIONS AND ANY INDIVIDUALS, ORGANIZATIONS, OR ASSOCIATES OF THE FOLLOWING INDIVIDUALS OR ORGANIZATIONS MAY NOT USE THIS SOFTWARE, ASSOCIATED DOCUMENTATION, OR ANY DERIVATIVES OF THE SOFTWARE WITHOUT EXPLICIT PERMISSION FROM NOOR NAHAS, THE ORIGINAL LICENSE HOLDER:
12 |
13 | - Louis Gualtieri Jr (Known as Melissa Gualtieri or Glitch) or any associates or projects of theirs.
14 |
15 | Reason: Breach of contract and currently owing 15,000 CAD through a small claims court order to Noor Nahas.
16 |
17 | - Michael Dzedzej
18 |
19 | Reason: Shitty FiveM developer who repackages open-source scripts and killed an RP community because of self esteem issues.
20 |
21 | - Bobby (Owner/"dev" of shit tier 100k or die server LA Noire)
22 |
23 | Reason: Space Cadets on top.
24 |
25 | - Any individuals previously part of the defunct Asylum RP staff team
26 |
27 | - blu / Chronus (owner of Hyperion and venom)
28 | Reason: Running terrible money grab servers with little to no roleplay, and generally a detriment to the spirit of roleplay and the FiveM community.
29 |
30 | - Fa0/Snow: owner of LARRP/Los Angeles Realism Roleplay
31 | Reason: Delinquent on several hundred dollars of payments to FiveM developers.
32 |
33 | - Tay McKenzie
34 | Reason: Attacking, attempting to dox, and creating a toxic environment for individuals based on their personal identities due to perceived insults and slights to Tay's fragile ego/personal pride.
35 |
--------------------------------------------------------------------------------
/client/menus/extras.lua:
--------------------------------------------------------------------------------
1 | local originalExtras = {}
2 | local extrasLastIndex = 1
3 |
4 | local function extras()
5 | local options = {}
6 | for i = 1, 14 do
7 | if not DoesExtraExist(vehicle, i) then
8 | goto continue
9 | end
10 |
11 | local extraTurnedOn = IsVehicleExtraTurnedOn(vehicle, i)
12 |
13 | options[#options + 1] = {
14 | label = ('Extra %d'):format(i),
15 | description = ('%s%s'):format(Config.Currency, Config.Prices['cosmetic']),
16 | close = true,
17 | values = {'Enabled', 'Disabled'},
18 | set = function(selected, index)
19 | SetVehicleExtra(vehicle, i, index - 1)
20 | return originalExtras[i] == (index - 1 == 0), ('%s %s'):format(options[selected].label, index == 1 and 'enabled' or 'disabled')
21 | end,
22 | restore = function()
23 | SetVehicleExtra(vehicle, i, not originalExtras[i])
24 | end,
25 | defaultIndex = extraTurnedOn and 1 or 2,
26 | }
27 | originalExtras[i] = extraTurnedOn
28 |
29 | ::continue::
30 | end
31 |
32 | return options
33 | end
34 |
35 | local menu = {
36 | id = 'customs-extras',
37 | canClose = true,
38 | disableInput = false,
39 | title = 'Extras',
40 | position = 'top-left',
41 | options = {},
42 | }
43 |
44 | local function onSubmit(selected, scrollIndex, args)
45 | local option = menu.options[selected]
46 |
47 | for _, v in pairs(menu.options) do
48 | v.restore()
49 | end
50 |
51 | local duplicate, desc = option.set(selected, scrollIndex)
52 |
53 | local success = require('client.utils.installMod')(duplicate, 'cosmetic', {
54 | description = desc,
55 | })
56 |
57 | if not success then menu.options[selected].restore() end
58 |
59 | lib.setMenuOptions('customs-extras', extras())
60 | lib.showMenu('customs-extras', extrasLastIndex)
61 | end
62 |
63 | menu.onSideScroll = function(selected, scrollIndex)
64 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
65 | local option = menu.options[selected]
66 | option.set(selected, scrollIndex)
67 | end
68 |
69 | menu.onSelected = function(selected, secondary, args)
70 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
71 | extrasLastIndex = selected
72 | end
73 |
74 | menu.onClose = function()
75 | for _, v in pairs(menu.options) do
76 | v.restore()
77 | end
78 | lib.showMenu(mainMenuId, mainLastIndex)
79 | end
80 |
81 | return function()
82 | menu.options = extras()
83 | lib.registerMenu(menu, onSubmit)
84 | return menu.id
85 | end
--------------------------------------------------------------------------------
/carmodcols_gen9.meta:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 | YKTA_MONOCHROME
6 |
7 |
8 |
9 | -
10 | YKTA_CHROMABERA
11 |
12 |
13 |
14 | -
15 | YKTA_ELECTRO
16 |
17 |
18 |
19 | -
20 | YKTA_SPRUNK_EX
21 |
22 |
23 |
24 | -
25 | YKTA_HSW
26 |
27 |
28 |
29 | -
30 | YKTA_NITE_DAY
31 |
32 |
33 |
34 | -
35 | YKTA_SUNSETS
36 |
37 |
38 |
39 | -
40 | YKTA_VICE_CITY
41 |
42 |
43 |
44 | -
45 | YKTA_SYNTHWAVE
46 |
47 |
48 |
49 | -
50 | YKTA_VERLIERER2
51 |
52 |
53 |
54 | -
55 | YKTA_TEMPERATUR
56 |
57 |
58 |
59 | -
60 | YKTA_FOUR_SEASO
61 |
62 |
63 |
64 | -
65 | YKTA_THE_SEVEN
66 |
67 |
68 |
69 | -
70 | YKTA_M9_THROWBA
71 |
72 |
73 |
74 | -
75 | YKTA_BUBBLEGUM
76 |
77 |
78 |
79 | -
80 | YKTA_FULL_RBOW
81 |
82 |
83 |
84 | -
85 | YKTA_KAMENRIDER
86 |
87 |
88 |
89 | -
90 | YKTA_CHRISTMAS
91 |
92 |
93 |
94 | -
95 | YKTA_MONIKA
96 |
97 |
98 |
99 | -
100 | YKTA_FUBUKI
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # popcornrp-customs
2 | A free-to-use and modify vehicle customization for FiveM using ox-lib
3 |
4 |
5 | https://github.com/alberttheprince/popcornrp-customs/assets/85725579/518b8ea9-d8f9-4026-8971-9bd4f1d4ab3f
6 |
7 |
8 | **Framework specific version by the community:**
9 | - [QBox](https://github.com/Qbox-project/qbx_customs)
10 | - [ND Framework](https://github.com/TheStoicBear/popcornrp-customs)
11 | - [ESX](https://github.com/itzdaimy/popcornrp-customs-ESX-)
12 | - QBCore/Oxcore (This one duh!)
13 |
14 | This release is a thank you to the FiveM open-source community and all the developers who have poured hours of their free time into creating helpful, free resources that I've used on my servers in the past and continue to use. Thank you!
15 |
16 | Thank you, Jorn (Discord: Jorn08), for his hard work on this resource and the Popcorn Roleplay Community for testing and supporting the development of this resource. Come join our server to experience our unique "Casually Serious" RP community!
17 |
18 | Popcorn Roleplay Discord: https://discord.gg/popcornroleplay
19 |
20 | Popcorn Roleplay server: connect 144.217.10.12
21 |
22 | Want to say thank you? We'd appreciate any donation on our Ko-Fi here: https://ko-fi.com/popcornrp.
23 |
24 | Popcornrp-Customs was created following the subsequent discovery of QB-Customs containing stolen/leaked code. Popcornrp-Customs is a total replacement for QB-Customs and is fully compatible with QB-Garages. However, Popcornrp-Customs only depends on ox_lib and can be used with any framework.
25 |
26 | Find Ox_lib Here: https://github.com/overextended/ox_lib
27 |
28 | This uses dragcam (already part of this resource) from Jorn: https://github.com/Jorn08/dragcam
29 |
30 | To use this resource, install ox_lib and this resource and ensure Popcornrp-Customs after ox_lib. All car customization locations can be set in the config.lua file.
31 |
32 | Preview: https://www.youtube.com/watch?v=8UcGmHJ3mUo
33 |
34 | Popcornrp-Customs allows you to set up the following:
35 |
36 | - Free EMS and Police Customization
37 | - Disable or Enable customization options
38 | - Change pricing
39 | - Drag your camera and zoom in and out
40 | - Air wrench sound to confirm the customization
41 | - Displayed pricing
42 | - All tire and paint options (including Chameleon)
43 | - Customize your car (duh)
44 | - Admin only customs menu /admincustoms
45 |
46 | You are free to modify this resource as per the attached license. **Please read and understand the license**, as it is a slightly modified MIT License.
47 |
48 | This resource is released with no support or promise of support. It is released as is, and we may make changes as we see fit. You may suggest changes, but we will only add or develop them if we see a need for that function for our server Popcorn Roleplay. We have thoroughly tested this resource with our small community, but feel free to report any issues that may arise.
49 |
50 | Thank you, and enjoy!
51 |
--------------------------------------------------------------------------------
/client/menus/paint.lua:
--------------------------------------------------------------------------------
1 | local originalPaint = {}
2 | local lastIndex
3 | local primaryPaint
4 |
5 | local function paintMods()
6 | local options = {}
7 |
8 | local primary, secondary = GetVehicleColours(vehicle)
9 | originalPaint.primary = primary
10 | originalPaint.secondary = secondary
11 |
12 | for category, values in pairs(Config.Paints) do
13 | local labels = {}
14 | local ids = {}
15 | local selectedIndex = 1
16 |
17 | for i, paint in ipairs(values) do
18 | labels[i] = paint.label
19 | ids[i] = paint.id
20 | if paint.id == primary then
21 | selectedIndex = i
22 | end
23 | end
24 |
25 | options[#options + 1] = {
26 | ids = ids,
27 | description = ('%s%s'):format(Config.Currency, Config.Prices['colors']),
28 | label = category,
29 | values = labels,
30 | close = true,
31 | defaultIndex = selectedIndex,
32 | }
33 | end
34 |
35 | table.sort(options, function(a, b)
36 | return a.label < b.label
37 | end)
38 |
39 | return options
40 | end
41 |
42 |
43 | local menu = {
44 | id = 'customs-paint',
45 | canClose = true,
46 | disableInput = false,
47 | position = 'top-left',
48 | options = {},
49 | }
50 |
51 | local function onSubmit(selected, scrollIndex, args)
52 | local option = menu.options[selected]
53 | local duplicate = option.ids[scrollIndex] == originalPaint[primaryPaint and 'primary' or 'secondary']
54 |
55 | local success = require('client.utils.installMod')(duplicate, 'colors', {
56 | description = ('%s applied'):format(option.values[scrollIndex]),
57 | icon = 'fas fa-paint-brush',
58 | })
59 |
60 | if not success then SetVehicleColours(vehicle, originalPaint.primary, originalPaint.secondary) end
61 |
62 | lib.setMenuOptions('customs-paint', paintMods())
63 | lib.showMenu('customs-paint', lastIndex)
64 | end
65 |
66 | menu.onClose = function(keyPressed)
67 | SetVehicleColours(vehicle, originalPaint.primary, originalPaint.secondary)
68 | lib.showMenu('customs-colors', colorsLastIndex)
69 | end
70 |
71 | menu.onSelected = function(selected, secondary, args)
72 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
73 | lastIndex = selected
74 | end
75 |
76 | menu.onSideScroll = function(selected, scrollIndex)
77 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
78 | local option = menu.options[selected]
79 | if primaryPaint then
80 | SetVehicleColours(vehicle, option.ids[scrollIndex], originalPaint.secondary)
81 | else
82 | SetVehicleColours(vehicle, originalPaint.primary, option.ids[scrollIndex])
83 | end
84 | end
85 |
86 | ---@param primary boolean
87 | return function(primary)
88 | primaryPaint = primary
89 | menu.options = paintMods()
90 | menu.title = primaryPaint and 'Primary paint' or 'Secondary paint'
91 | lib.registerMenu(menu, onSubmit)
92 | return menu.id
93 | end
94 |
--------------------------------------------------------------------------------
/client/menus/colors.lua:
--------------------------------------------------------------------------------
1 | colorsLastIndex = 1
2 |
3 | local function colors()
4 | local options = {}
5 |
6 | options[#options+1] = {
7 | label = 'Paint primary',
8 | close = true,
9 | args = {
10 | menu = 'client.menus.paint',
11 | menuArgs = {
12 | true,
13 | }
14 | }
15 | }
16 |
17 | options[#options+1] = {
18 | label = 'Paint secondary',
19 | close = true,
20 | args = {
21 | menu = 'client.menus.paint',
22 | menuArgs = {
23 | false,
24 | }
25 | }
26 | }
27 |
28 | options[#options+1] = {
29 | label = 'Neon',
30 | close = true,
31 | args = {
32 | menu = 'client.menus.neon',
33 | }
34 | }
35 |
36 | options[#options+1] = require('client.options.xenon')()
37 | options[#options+1] = require('client.options.pearlescent')()
38 | options[#options+1] = require('client.options.wheelcolor')()
39 | options[#options+1] = require('client.options.windowtint')()
40 | options[#options+1] = require('client.options.tyresmoke')()
41 | options[#options+1] = require('client.options.dashboard')()
42 | options[#options+1] = require('client.options.interior')()
43 |
44 | local liveryOption = require('client.options.livery')()
45 | if #liveryOption.values > 0 then
46 | options[#options+1] = liveryOption
47 | end
48 |
49 | table.sort(options, function(a, b)
50 | return a.label < b.label
51 | end)
52 |
53 | return options
54 | end
55 |
56 | local menu = {
57 | id = 'customs-colors',
58 | title = 'Cosmetics - Colors',
59 | canClose = true,
60 | disableInput = false,
61 | options = {},
62 | position = 'top-left',
63 | }
64 |
65 | local function onSubmit(selected, scrollIndex, args)
66 | for _, v in pairs(menu.options) do
67 | if not v.args?.menu then v.restore() end
68 | end
69 |
70 | local subMenuName = args?.menu
71 | if subMenuName then
72 | local menuId = require(subMenuName)(args?.menuArgs and table.unpack(args.menuArgs))
73 | lib.showMenu(menuId, 1)
74 | return
75 | end
76 |
77 |
78 | local duplicate, desc = menu.options[selected].set(scrollIndex)
79 |
80 | local success = require('client.utils.installMod')(duplicate, 'colors', {
81 | description = desc,
82 | icon = 'fa-solid fa-spray-can',
83 | })
84 |
85 | if not success then menu.options[selected].restore() end
86 |
87 | lib.setMenuOptions(menu.id, colors())
88 | lib.showMenu(menu.id, colorsLastIndex)
89 | end
90 |
91 | menu.onSideScroll = function(selected, scrollIndex)
92 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
93 | local option = menu.options[selected]
94 | option.set(scrollIndex)
95 | end
96 |
97 | menu.onClose = function()
98 | for _, v in pairs(menu.options) do
99 | if not v.args?.menu then v.restore() end -- v.args.menu means it's a submenu
100 | end
101 | lib.showMenu(mainMenuId, mainLastIndex)
102 | end
103 |
104 | menu.onSelected = function(selected)
105 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
106 | colorsLastIndex = selected
107 | end
108 |
109 | return function()
110 | menu.options = colors()
111 | lib.registerMenu(menu, onSubmit)
112 | return menu.id
113 | end
--------------------------------------------------------------------------------
/client/menus/neon.lua:
--------------------------------------------------------------------------------
1 | local originalNeon = {}
2 | local lastIndex = 1
3 | local originalLabelIndex = 1
4 |
5 | local function neon()
6 | local options = {}
7 |
8 | for i = 1, 4 do
9 | local enabled = IsVehicleNeonLightEnabled(vehicle, i - 1)
10 | originalNeon[i] = enabled
11 |
12 | options[i] = {
13 | label = ('Neon %s'):format(Config.Neon[i].label),
14 | description = ('%s%s'):format(Config.Currency, Config.Prices['colors']),
15 | values = {
16 | 'Disabled',
17 | 'Enabled',
18 | },
19 | close = true,
20 | defaultIndex = enabled and 2 or 1,
21 | set = function(index)
22 | SetVehicleNeonLightEnabled(vehicle, i - 1, index == 2)
23 | return originalNeon[i] == (index == 2), ("Neon %s %s"):format(Config.Neon[i].label, index == 2 and 'enabled' or 'disabled')
24 | end,
25 | restore = function()
26 | SetVehicleNeonLightEnabled(vehicle, i - 1, originalNeon[i])
27 | end
28 | }
29 | end
30 |
31 | local r, g, b = GetVehicleNeonLightsColour(vehicle)
32 |
33 | local rgbValues = {}
34 | local neonLabels = {}
35 | for i, v in ipairs(Config.NeonColors) do
36 | neonLabels[i] = v.label
37 | rgbValues[i] = {r = v.r, g = v.g, b = v.b}
38 | if v.r == r and v.g == g and v.b == b then
39 | originalLabelIndex = i
40 | end
41 | end
42 |
43 | options[5] = {
44 | label = 'Neon color',
45 | close = true,
46 | values = neonLabels,
47 | rgbValues = rgbValues,
48 | set = function(index)
49 | local rgb = Config.NeonColors[index]
50 | SetVehicleNeonLightsColour(vehicle, rgb.r, rgb.g, rgb.b)
51 | return originalLabelIndex == index, ('%s neon installed'):format(Config.NeonColors[index].label)
52 | end,
53 | restore = function()
54 | local rgb = Config.NeonColors[originalLabelIndex]
55 | SetVehicleNeonLightsColour(vehicle, rgb.r, rgb.g, rgb.b)
56 | end,
57 | defaultIndex = originalLabelIndex,
58 | }
59 |
60 | return options
61 | end
62 |
63 | local menu = {
64 | id = 'customs-neon',
65 | canClose = true,
66 | disableInput = false,
67 | title = 'Neon',
68 | position = 'top-left',
69 | options = {},
70 | }
71 |
72 | local function onSubmit(selected, scrollIndex, args)
73 | local option = menu.options[selected]
74 |
75 | for _, v in pairs(menu.options) do
76 | v.restore()
77 | end
78 |
79 | local duplicate, desc = option.set(scrollIndex)
80 |
81 | local success = require('client.utils.installMod')(duplicate, 'colors', {
82 | description = desc,
83 | })
84 |
85 | if not success then menu.options[selected].restore() end
86 |
87 | lib.setMenuOptions(menu.id, neon())
88 | lib.showMenu(menu.id, lastIndex)
89 | end
90 |
91 | menu.onSideScroll = function(selected, scrollIndex)
92 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
93 | local option = menu.options[selected]
94 | option.set(scrollIndex)
95 | end
96 |
97 | menu.onSelected = function(selected)
98 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
99 | lastIndex = selected
100 | end
101 |
102 | menu.onClose = function()
103 | for _, v in pairs(menu.options) do
104 | v.restore()
105 | end
106 | lib.showMenu('customs-colors', colorsLastIndex)
107 | end
108 |
109 | return function()
110 | menu.options = neon()
111 | lib.registerMenu(menu, onSubmit)
112 | return menu.id
113 | end
--------------------------------------------------------------------------------
/client/menus/parts.lua:
--------------------------------------------------------------------------------
1 | local getModLabel = require('client.utils.getModLabel')
2 | local originalMods = {}
3 | partsLastIndex = 1
4 | local VehicleClass = require('client.utils.enums.VehicleClass')
5 |
6 | local function parts()
7 | local options = {}
8 |
9 | for _, mod in ipairs(Config.Mods) do
10 | local modCount = GetNumVehicleMods(vehicle, mod.id)
11 |
12 | if mod.category ~= 'parts'
13 | or mod.enabled == false
14 | or modCount == 0
15 | then
16 | goto continue
17 | end
18 |
19 | local modLabels = {}
20 | modLabels[1] = 'Stock'
21 | for i = -1, modCount - 1 do
22 | modLabels[i + 2] = getModLabel(vehicle, mod.id, i)
23 | end
24 |
25 | local currentMod = GetVehicleMod(vehicle, mod.id)
26 | originalMods[mod.id] = currentMod
27 |
28 | options[#options + 1] = {
29 | id = mod.id,
30 | label = mod.label,
31 | description = ('%s%s'):format(Config.Currency, Config.Prices['cosmetic']),
32 | values = modLabels,
33 | close = true,
34 | defaultIndex = currentMod + 2,
35 | set = function(index)
36 | SetVehicleMod(vehicle, mod.id, index - 2, false)
37 | return originalMods[mod.id] == index - 2, ('%s installed'):format(modLabels[index])
38 | end,
39 | restore = function()
40 | SetVehicleMod(vehicle, mod.id, originalMods[mod.id], false)
41 | end,
42 | }
43 |
44 | ::continue::
45 | end
46 |
47 | if GetVehicleClass(vehicle) ~= VehicleClass.Cycles then
48 | options[#options + 1] = {
49 | label = 'Wheels',
50 | close = true,
51 | args = {
52 | menu = 'client.menus.wheels',
53 | }
54 | }
55 | end
56 |
57 | options[#options + 1] = require('client.options.plateindex')()
58 |
59 | table.sort(options, function(a, b)
60 | return a.label < b.label
61 | end)
62 |
63 | return options
64 | end
65 |
66 | local menu = {
67 | id = 'customs-parts',
68 | title = 'Cosmetics - Parts',
69 | canClose = true,
70 | disableInput = false,
71 | options = {},
72 | position = 'top-left',
73 | }
74 |
75 | local function onSubmit(selected, scrollIndex, args)
76 | for _, v in pairs(menu.options) do
77 | if not v.args?.menu then v.restore() end
78 | end
79 |
80 | local subMenuName = args?.menu
81 | if subMenuName then
82 | local menuId = require(subMenuName)(args?.menuArgs and table.unpack(args.menuArgs))
83 | lib.showMenu(menuId, 1)
84 | return
85 | end
86 |
87 |
88 | local duplicate, desc = menu.options[selected].set(scrollIndex)
89 |
90 | local success = require('client.utils.installMod')(duplicate, 'cosmetic', {
91 | description = desc,
92 | })
93 |
94 | if not success then menu.options[selected].restore() end
95 |
96 | lib.setMenuOptions(menu.id, parts())
97 | lib.showMenu(menu.id, partsLastIndex)
98 | end
99 |
100 | menu.onSideScroll = function(selected, scrollIndex)
101 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
102 | local option = menu.options[selected]
103 | option.set(scrollIndex)
104 | end
105 |
106 | menu.onClose = function()
107 | for _, v in pairs(menu.options) do
108 | if not v.args?.menu then v.restore() end
109 | end
110 | lib.showMenu(mainMenuId, mainLastIndex)
111 | end
112 |
113 | menu.onSelected = function(selected)
114 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
115 | partsLastIndex = selected
116 | end
117 |
118 | return function()
119 | menu.options = parts()
120 | lib.registerMenu(menu, onSubmit)
121 | return menu.id
122 | end
123 |
--------------------------------------------------------------------------------
/client/zones.lua:
--------------------------------------------------------------------------------
1 | local zoneId
2 | local QBCore
3 | local allowAccess = false
4 | local zones = {}
5 | local textUiTitle = 'Press [E] to tune your car'
6 | ---@type TextUIOptions
7 | local textUiOptions = {
8 | icon = 'fa-solid fa-car',
9 | position = 'left-center',
10 | }
11 |
12 | if GetResourceState('qb-core') == 'started' then
13 | QBCore = exports['qb-core']:GetCoreObject()
14 | end
15 |
16 | ---@param vertices vector3[]
17 | ---@return vector3
18 | local function calculatePolyzoneCenter(vertices)
19 | local xSum = 0
20 | local ySum = 0
21 | local zSum = 0
22 |
23 | for i = 1, #vertices do
24 | xSum = xSum + vertices[i].x
25 | ySum = ySum + vertices[i].y
26 | zSum = zSum + vertices[i].z
27 | end
28 |
29 | local center = vec3(xSum / #vertices, ySum / #vertices, zSum / #vertices)
30 |
31 | return center
32 | end
33 |
34 | CreateThread(function()
35 | for _, v in ipairs(Config.Zones) do
36 | zones[#zones + 1] = lib.zones.poly({
37 | points = v.points,
38 | onEnter = function(s)
39 | zoneId = s.id
40 | if not cache.vehicle then return end
41 | local hasJob = true
42 | if v.job and QBCore then
43 | hasJob = false
44 | local playerJob = QBCore.Functions.GetPlayerData().job.name
45 | for _, job in ipairs(v.job) do
46 | if playerJob == job then
47 | hasJob = true
48 | break
49 | end
50 | end
51 | end
52 |
53 | allowAccess = hasJob
54 | if not hasJob then
55 | return
56 | end
57 |
58 | lib.showTextUI(textUiTitle, textUiOptions)
59 | end,
60 | onExit = function()
61 | zoneId = nil
62 | lib.hideTextUI()
63 | end,
64 | inside = function()
65 | if IsControlJustPressed(0, 38) and cache.vehicle and allowAccess then
66 | SetEntityVelocity(cache.vehicle, 0.0, 0.0, 0.0)
67 | lib.hideTextUI()
68 | require('client.menus.main')()
69 | end
70 | end,
71 | })
72 |
73 | if not v.hideBlip then
74 | local center = calculatePolyzoneCenter(v.points)
75 | local blip = AddBlipForCoord(center.x, center.y, center.z)
76 | SetBlipSprite(blip, 72)
77 | SetBlipColour(blip, v.blipColor or 0)
78 | SetBlipScale(blip, 0.8)
79 | SetBlipAsShortRange(blip, true)
80 | BeginTextCommandSetBlipName('STRING')
81 | AddTextComponentSubstringPlayerName(v.blipLabel or 'Customs')
82 | EndTextCommandSetBlipName(blip)
83 | end
84 | end
85 | end)
86 |
87 | lib.callback.register('customs:client:zone', function()
88 | return zoneId
89 | end)
90 |
91 | RegisterNetEvent('customs:client:adminMenu', function()
92 | local perm = lib.callback.await('customs:server:checkPerms')
93 | if perm then
94 | SetEntityVelocity(cache.vehicle, 0.0, 0.0, 0.0)
95 | require('client.menus.main')()
96 | else
97 | lib.notify({
98 | title = 'Customs',
99 | description = 'Cound not access menu or no vehicle present',
100 | position = 'top',
101 | type = 'error'
102 | })
103 | end
104 | end)
105 |
106 | lib.onCache("vehicle", function(veh)
107 | if not veh then
108 | lib.hideTextUI()
109 | return
110 | end
111 | for _, zone in ipairs(zones) do
112 | if zone:contains(GetEntityCoords(veh)) then
113 | lib.showTextUI(textUiTitle, textUiOptions)
114 | break
115 | end
116 | end
117 | end)
118 |
--------------------------------------------------------------------------------
/client/menus/performance.lua:
--------------------------------------------------------------------------------
1 | local getModLabel = require('client.utils.getModLabel')
2 | local originalMods = {}
3 | local originalTurbo
4 | local lastIndex
5 | local VehicleClass = require('client.utils.enums.VehicleClass')
6 |
7 | local function priceLabel(price)
8 | if type(price) ~= 'table' then
9 | return ('%s%s'):format(Config.Currency, price)
10 | end
11 | local copy = table.clone(price)
12 | table.remove(copy, 1)
13 | for i = 1, #copy do
14 | copy[i] = ('%d: %s%s'):format(i, Config.Currency, copy[i])
15 | end
16 | return table.concat(copy, ' | ')
17 | end
18 |
19 | local function performance()
20 | local options = {}
21 | for _, mod in ipairs(Config.Mods) do
22 | local modCount = GetNumVehicleMods(vehicle, mod.id)
23 | if mod.category ~= 'performance'
24 | or mod.enabled == false
25 | or modCount == 0
26 | then goto continue end
27 | local modLabels = {}
28 | modLabels[1] = 'Stock'
29 | for i = -1, modCount - 1 do
30 | modLabels[i + 2] = getModLabel(vehicle, mod.id, i)
31 | end
32 | local currentMod = GetVehicleMod(vehicle, mod.id)
33 | originalMods[mod.id] = currentMod
34 | options[#options+1] = {
35 | id = mod.id,
36 | label = mod.label,
37 | description = priceLabel(Config.Prices[mod.id]),
38 | values = modLabels,
39 | close = true,
40 | defaultIndex = currentMod + 2,
41 | set = function(index)
42 | SetVehicleMod(vehicle, mod.id, index - 2, false)
43 | return currentMod == index - 2, ('%s installed'):format(modLabels[index])
44 | end,
45 | restore = function()
46 | SetVehicleMod(vehicle, mod.id, originalMods[mod.id], false)
47 | end
48 | }
49 | ::continue::
50 | end
51 | originalTurbo = IsToggleModOn(vehicle, 18)
52 | if GetVehicleClass(vehicle) ~= VehicleClass.Cycles then
53 | options[#options+1] = {
54 | id = 18,
55 | label = 'Turbo',
56 | description = ('%s%s'):format(Config.Currency, Config.Prices[18]),
57 | values = {'Disabled', 'Enabled'},
58 | close = true,
59 | defaultIndex = originalTurbo and 2 or 1,
60 | set = function(index)
61 | ToggleVehicleMod(vehicle, 18, index == 2)
62 | return originalTurbo == (index == 2), ('Turbo %s'):format(index == 2 and 'enabled' or 'disabled')
63 | end,
64 | restore = function()
65 | ToggleVehicleMod(vehicle, 18, originalTurbo)
66 | end
67 | }
68 | end
69 | table.sort(options, function(a, b)
70 | return a.label < b.label
71 | end)
72 | return options
73 | end
74 |
75 | local menu = {
76 | id = 'customs-performance',
77 | title = 'Performance',
78 | canClose = true,
79 | disableInput = false,
80 | options = {},
81 | position = 'top-left',
82 | }
83 |
84 | local function onSubmit(selected, scrollIndex)
85 | for _, v in pairs(menu.options) do
86 | v.restore()
87 | end
88 | local duplicate, desc = menu.options[selected].set(scrollIndex)
89 | local success = require('client.utils.installMod')(duplicate, menu.options[selected].id, {
90 | description = desc,
91 | }, scrollIndex)
92 | if not success then menu.options[selected].restore() end
93 | lib.setMenuOptions(menu.id, performance())
94 | lib.showMenu(menu.id, lastIndex)
95 | end
96 |
97 | menu.onSideScroll = function(selected, scrollIndex)
98 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
99 | local option = menu.options[selected]
100 | option.set(scrollIndex)
101 | end
102 |
103 | menu.onClose = function()
104 | for _, v in pairs(menu.options) do
105 | v.restore()
106 | end
107 | lib.showMenu(mainMenuId, mainLastIndex)
108 | end
109 |
110 | menu.onSelected = function(selected)
111 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
112 | lastIndex = selected
113 | end
114 |
115 | return function()
116 | menu.options = performance()
117 |
118 | if #menu.options == 0 then
119 | lib.notify({
120 | title = 'Customs',
121 | description = 'This vehicle has no performance upgrades available',
122 | position = 'top',
123 | type = 'info'
124 | })
125 |
126 | return mainMenuId
127 | end
128 |
129 | lib.registerMenu(menu, onSubmit)
130 | return menu.id
131 | end
132 |
--------------------------------------------------------------------------------
/client/menus/main.lua:
--------------------------------------------------------------------------------
1 | mainLastIndex = 1
2 | vehicle = 0
3 | mainMenuId = 'customs-main'
4 | local QBCore
5 | local inMenu = false
6 | local dragcam = require('client.dragcam')
7 | local startDragCam = dragcam.startDragCam
8 | local stopDragCam = dragcam.stopDragCam
9 |
10 | if GetResourceState('qb-core') == 'started' then
11 | QBCore = exports['qb-core']:GetCoreObject()
12 | end
13 |
14 | local menu = {
15 | id = mainMenuId,
16 | canClose = true,
17 | disableInput = false,
18 | title = 'Popcorn Customs',
19 | position = 'top-left',
20 | options = {},
21 | }
22 |
23 | local function main()
24 | if GetVehicleBodyHealth(vehicle) < 1000.0 then
25 | return {{
26 | label = 'Repair',
27 | description = ('%s%d'):format(Config.Currency, math.ceil(1000 - GetVehicleBodyHealth(vehicle))),
28 | close = true,
29 | }}
30 | end
31 |
32 | local options = {
33 | {
34 | label = 'Performance',
35 | close = true,
36 | args = {
37 | menu = 'client.menus.performance',
38 | }
39 | },
40 | {
41 | label = 'Cosmetics - Parts',
42 | close = true,
43 | args = {
44 | menu = 'client.menus.parts',
45 | }
46 | },
47 | {
48 | label = 'Cosmetics - Colors',
49 | close = true,
50 | args = {
51 | menu = 'client.menus.colors',
52 | }
53 | },
54 | }
55 |
56 | if DoesExtraExist(vehicle, 1) then
57 | options[#options + 1] = {
58 | label = 'Extras',
59 | close = true,
60 | args = {
61 | menu = 'client.menus.extras',
62 | }
63 | }
64 | end
65 |
66 | return options
67 | end
68 |
69 | local function disableControls()
70 | inMenu = true
71 | CreateThread(function()
72 | while inMenu do
73 | Wait(0)
74 | DisableControlAction(0, 71, true) -- accelerating
75 | DisableControlAction(0, 72, true) -- decelerating
76 | for i = 81, 85 do -- radio stuff
77 | DisableControlAction(0, i, true)
78 | end
79 | DisableControlAction(0, 106, true) -- turning vehicle wheels
80 | end
81 | end)
82 | end
83 |
84 | local function repair()
85 | local success = lib.callback.await('customs:server:repair', false, GetVehicleBodyHealth(vehicle))
86 | if success then
87 | lib.notify({
88 | title = 'Customs',
89 | description = 'Vehicle repaired!',
90 | position = 'top',
91 | type = 'success'
92 | })
93 | SendNUIMessage({sound = true})
94 | SetVehicleBodyHealth(vehicle, 1000.0)
95 | SetVehicleEngineHealth(vehicle, 1000.0)
96 | local fuelLevel = GetVehicleFuelLevel(vehicle)
97 | SetVehicleFixed(vehicle)
98 | SetVehicleFuelLevel(vehicle, fuelLevel)
99 | else
100 | lib.notify({
101 | title = 'Customs',
102 | description = 'You don\'t have enough money!',
103 | position = 'top',
104 | type = 'error'
105 | })
106 | end
107 |
108 | menu.options = main()
109 | lib.setMenuOptions(menu.id, menu.options)
110 | lib.showMenu(menu.id, 1)
111 | end
112 |
113 | local function onSubmit(selected, scrollIndex, args)
114 | if menu.options[selected].label == 'Repair' then
115 | lib.hideMenu(false)
116 | repair()
117 | return
118 | end
119 | local menuId = require(args.menu)()
120 | lib.showMenu(menuId, 1)
121 | end
122 |
123 | menu.onSelected = function(selected)
124 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
125 | mainLastIndex = selected
126 | end
127 |
128 | menu.onClose = function()
129 | inMenu = false
130 | stopDragCam()
131 | if not lib.callback.await('customs:server:adminMenuOpened') then
132 | lib.showTextUI('Press [E] to tune your car', {
133 | icon = 'fa-solid fa-car',
134 | position = 'left-center',
135 | })
136 | end
137 | if QBCore then
138 | TriggerServerEvent("customs:server:saveVehicleProps")
139 | end
140 | end
141 |
142 | lib.callback.register('customs:client:vehicleProps', function()
143 | return QBCore.Functions.GetVehicleProperties(vehicle)
144 | end)
145 |
146 | return function()
147 | if not cache.vehicle or inMenu then return end
148 | vehicle = cache.vehicle
149 | SetVehicleModKit(vehicle, 0)
150 | menu.options = main()
151 | lib.registerMenu(menu, onSubmit)
152 | lib.showMenu(menu.id, 1)
153 | disableControls()
154 | startDragCam(vehicle)
155 | end
156 |
--------------------------------------------------------------------------------
/server.lua:
--------------------------------------------------------------------------------
1 | local QBCore
2 | local currentAdmins = {}
3 | if GetResourceState('qb-core') == 'started' then
4 | QBCore = exports['qb-core']:GetCoreObject()
5 | lib.addAce('group.admin', 'customs.admin')
6 | else
7 | warn('qb-core is missing, modifications won\'t cost anything')
8 | end
9 |
10 | ---@return number
11 | local function getModPrice(mod, level)
12 | if mod == 'cosmetic' or mod == 'colors' or mod == 18 then
13 | return Config.Prices[mod] --[[@as number]]
14 | else
15 | return Config.Prices[mod][level]
16 | end
17 | end
18 |
19 | ---@param source number
20 | ---@param amount number
21 | ---@return boolean
22 | local function removeMoney(source, amount)
23 | if not QBCore then return true end
24 | local player = QBCore.Functions.GetPlayer(source)
25 | local cashBalance = player.Functions.GetMoney('cash')
26 | local bankBalance = player.Functions.GetMoney('bank')
27 |
28 | if cashBalance >= amount then
29 | player.Functions.RemoveMoney('cash', amount, "Customs")
30 | return true
31 | elseif bankBalance >= amount then
32 | player.Functions.RemoveMoney('bank', amount, "Customs")
33 | lib.notify(source, {
34 | title = 'Customs',
35 | description = ('You paid $%s from your bank account'):format(amount),
36 | type = 'success',
37 | })
38 | return true
39 | end
40 |
41 | return false
42 | end
43 |
44 | lib.callback.register('customs:server:checkPerms', function(source)
45 | if IsPlayerAceAllowed(source, 'customs.admin') then
46 | return true
47 | else
48 | return false
49 | end
50 | end)
51 |
52 | -- Won't charge money for mods if the player's job is in the list
53 | lib.callback.register('customs:server:pay', function(source, mod, level)
54 | local zone = lib.callback.await('customs:client:zone', source)
55 |
56 | if currentAdmins[source] then
57 | if currentAdmins[source].admin then
58 | return true
59 | end
60 | end
61 |
62 | for i, v in ipairs(Config.Zones) do
63 | if i == zone and v.freeMods then
64 | local playerJob = QBCore.Functions.GetPlayer(source)?.PlayerData?.job?.name
65 | for _, job in ipairs(v.freeMods) do
66 | if playerJob == job then
67 | return true
68 | end
69 | end
70 | end
71 | end
72 |
73 | return removeMoney(source, getModPrice(mod, level))
74 | end)
75 |
76 | -- Won't charge money for repairs if the player's job is in the list
77 | lib.callback.register('customs:server:repair', function(source, bodyHealth)
78 | local zone = lib.callback.await('customs:client:zone', source)
79 |
80 | if currentAdmins[source] then
81 | if currentAdmins[source].admin then
82 | return true
83 | end
84 | end
85 |
86 | for i, v in ipairs(Config.Zones) do
87 | if i == zone and v.freeRepair then
88 | local playerJob = QBCore.Functions.GetPlayer(source)?.PlayerData?.job?.name
89 | for _, job in ipairs(v.freeRepair) do
90 | if playerJob == job then
91 | return true
92 | end
93 | end
94 | end
95 | end
96 |
97 | local price = math.ceil(1000 - bodyHealth)
98 | return removeMoney(source, price)
99 | end)
100 |
101 | lib.callback.register('customs:server:adminMenuOpened', function(source)
102 | if currentAdmins[source] then
103 | if currentAdmins[source].admin then
104 | return true
105 | end
106 | end
107 | return false
108 | end)
109 |
110 | local function IsVehicleOwned(plate)
111 | local result = MySQL.scalar.await('SELECT 1 from player_vehicles WHERE plate = ?', {plate})
112 | if result then
113 | return true
114 | else
115 | return false
116 | end
117 | end
118 |
119 | --Copied from qb-mechanicjob
120 | RegisterNetEvent('customs:server:saveVehicleProps', function()
121 | local src = source --[[@as number]]
122 | local vehicleProps = lib.callback.await('customs:client:vehicleProps', src)
123 | currentAdmins[src] = currentAdmins[src] or {}
124 | currentAdmins[src].admin = false
125 | if IsVehicleOwned(vehicleProps.plate) then
126 | MySQL.update.await('UPDATE player_vehicles SET mods = ? WHERE plate = ?', {json.encode(vehicleProps), vehicleProps.plate})
127 | end
128 | end)
129 |
130 | --Commands
131 | lib.addCommand('admincustoms', {
132 | help = 'Toggle customs menu for admins',
133 | restricted = 'customs.admin',
134 | }, function(source, args, raw)
135 | currentAdmins[source] = currentAdmins[source] or {}
136 | currentAdmins[source].admin = true
137 | TriggerClientEvent('customs:client:adminMenu', source)
138 | end)
--------------------------------------------------------------------------------
/client/menus/wheels.lua:
--------------------------------------------------------------------------------
1 | local originalWheelType
2 | local originalMod
3 | local originalRearWheel
4 | local lastIndex = 1
5 | local WheelType = require('client.utils.enums.WheelType')
6 | local VehicleClass = require('client.utils.enums.VehicleClass')
7 |
8 | ---@param wheelType WheelType
9 | local function isWheelTypeAllowed(wheelType)
10 | local class = GetVehicleClass(vehicle)
11 | if class == VehicleClass.Cycles then return false end
12 |
13 | if class == VehicleClass.Motorcycles then
14 | if wheelType == WheelType.Bike then
15 | return true
16 | end
17 | return false
18 | end
19 |
20 | if class == VehicleClass.OpenWheels then
21 | if wheelType == WheelType.OpenWheel then
22 | return true
23 | end
24 | return false
25 | end
26 | return true
27 | end
28 |
29 | local function wheels()
30 | local options = {}
31 |
32 | originalWheelType = GetVehicleWheelType(vehicle)
33 | originalMod = GetVehicleMod(vehicle, 23)
34 |
35 | for _, category in ipairs(Config.Wheels) do
36 | if not isWheelTypeAllowed(category.id) then goto continue end
37 | SetVehicleWheelType(vehicle, category.id)
38 | local modCount = GetNumVehicleMods(vehicle, 23)
39 | local labels = {}
40 | for j = 1, modCount do
41 | labels[j] = GetLabelText(GetModTextLabel(vehicle, 23, j - 1))
42 | end
43 |
44 | options[#options + 1] = {
45 | id = category.id,
46 | label = category.label,
47 | description = ('%s%s'):format(Config.Currency, Config.Prices['cosmetic']),
48 | values = labels,
49 | close = true,
50 | set = function(wheelType, index)
51 | SetVehicleWheelType(vehicle, wheelType)
52 | SetVehicleMod(vehicle, 23, index - 1, false)
53 | end,
54 | defaultIndex = originalWheelType == category.id and originalMod + 1 or 1
55 | }
56 |
57 | if GetVehicleClass(vehicle) == VehicleClass.Motorcycles then
58 | originalRearWheel = GetVehicleMod(vehicle, 24)
59 | options[#options + 1] = {
60 | id = 'rear',
61 | label = 'Bike rear wheel',
62 | values = labels,
63 | close = true,
64 | set = function(_, index)
65 | SetVehicleWheelType(vehicle, 6)
66 | SetVehicleMod(vehicle, 24, index - 1, false)
67 | end,
68 | defaultIndex = originalWheelType == 6 and originalRearWheel + 1 or 1,
69 | }
70 | end
71 |
72 | ::continue::
73 | end
74 |
75 | SetVehicleWheelType(vehicle, originalWheelType)
76 |
77 | table.sort(options, function(a, b)
78 | if a.id == 'rear' then return false end
79 | return a.label < b.label
80 | end)
81 |
82 | return options
83 | end
84 |
85 | local menu = {
86 | id = 'customs-wheels',
87 | canClose = true,
88 | disableInput = false,
89 | title = 'Wheels',
90 | position = 'top-left',
91 | options = {}
92 | }
93 |
94 | local function onSubmit(selected, scrollIndex)
95 | local option = menu.options[selected]
96 | local label = option.values[scrollIndex]
97 | local duplicate = option.id == originalWheelType and scrollIndex - 1 == originalMod
98 |
99 | if option.id == 6 then
100 | SetVehicleMod(vehicle, 24, originalRearWheel, false)
101 | elseif option.id == 'rear' then
102 | SetVehicleMod(vehicle, 23, originalMod, false)
103 | end
104 |
105 | local success = require('client.utils.installMod')(duplicate, 'cosmetic', {
106 | description = ('%s %s installed'):format(option.label, label),
107 | })
108 |
109 | if not success then
110 | SetVehicleWheelType(vehicle, originalWheelType)
111 | SetVehicleMod(vehicle, 23, originalMod, false)
112 | SetVehicleMod(vehicle, 24, originalRearWheel, false)
113 | end
114 |
115 | lib.setMenuOptions(menu.id, wheels())
116 | lib.showMenu(menu.id, lastIndex)
117 | end
118 |
119 | menu.onSideScroll = function(selected, scrollIndex)
120 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
121 | local option = menu.options[selected]
122 | option.set(option.id, scrollIndex)
123 | end
124 |
125 | menu.onSelected = function(selected)
126 | PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
127 | lastIndex = selected
128 | end
129 |
130 | menu.onClose = function()
131 | SetVehicleWheelType(vehicle, originalWheelType)
132 | SetVehicleMod(vehicle, 23, originalMod, false)
133 | SetVehicleMod(vehicle, 24, originalRearWheel, false)
134 | lib.showMenu('customs-parts', partsLastIndex)
135 | end
136 |
137 | return function()
138 | menu.options = wheels()
139 | lib.registerMenu(menu, onSubmit)
140 | return menu.id
141 | end
--------------------------------------------------------------------------------
/client/dragcam.lua:
--------------------------------------------------------------------------------
1 | local angleY = 0.0
2 | local angleZ = 0.0
3 | local cam = nil
4 | local running = false
5 | local gEntity = nil
6 | local gRadius = nil
7 | local gRadiusMax = nil
8 | local gRadiusMin = nil
9 | local scaleform = nil
10 | local scrollIncrements = nil
11 |
12 | local function cos(degrees)
13 | return math.cos(math.rad(degrees))
14 | end
15 |
16 | local function sin(degrees)
17 | return math.sin(math.rad(degrees))
18 | end
19 |
20 | local function setCamPosition()
21 | local entityCoords = GetEntityCoords(gEntity)
22 | local mouseX = GetDisabledControlNormal(0, 1) * 8.0 -- 8x multiplier to make it more sensitive
23 | local mouseY = GetDisabledControlNormal(0, 2) * 8.0
24 |
25 | angleZ = angleZ - mouseX -- left / right
26 | angleY = angleY + mouseY -- up / down
27 | angleY = math.clamp(angleY, 0.0, 89.0) -- >=90 degrees will flip the camera, < 0 is underground
28 |
29 | local cosAngleZ = cos(angleZ)
30 | local cosAngleY = cos(angleY)
31 | local sinAngleZ = sin(angleZ)
32 | local sinAngleY = sin(angleY)
33 |
34 | local offset = vec3(
35 | ((cosAngleZ * cosAngleY) + (cosAngleY * cosAngleZ)) / 2 * gRadius,
36 | ((sinAngleZ * cosAngleY) + (cosAngleY * sinAngleZ)) / 2 * gRadius,
37 | ((sinAngleY)) * gRadius
38 | )
39 |
40 | local camPos = vec3(entityCoords.x + offset.x, entityCoords.y + offset.y, entityCoords.z + offset.z)
41 | SetCamCoord(cam, camPos.x, camPos.y, camPos.z)
42 | PointCamAtCoord(cam, entityCoords.x, entityCoords.y, entityCoords.z + 0.5)
43 | end
44 |
45 | local function disablePlayerMovement()
46 | DisableControlAction(0, 21, true) -- INPUT_SPRINT | LEFT SHIFT
47 | DisableControlAction(0, 24, true) -- INPUT_ATTACK | LEFT MOUSE BUTTON
48 | DisableControlAction(0, 25, true) -- INPUT_AIM | RIGHT MOUSE BUTTON
49 | DisableControlAction(0, 30, true) -- INPUT_MOVE_LR | D
50 | DisableControlAction(0, 31, true) -- INPUT_MOVE_UD | S
51 | DisableControlAction(0, 36, true) -- INPUT_DUCK | LEFT CTRL
52 | DisableControlAction(0, 47, true) -- INPUT_DETONATE | G
53 | DisableControlAction(0, 58, true) -- INPUT_THROW_GRENADE | G
54 | DisableControlAction(0, 69, true) -- INPUT_VEH_ATTACK | LEFT MOUSE BUTTON
55 | DisableControlAction(0, 75, true) -- INPUT_VEH_EXIT | F
56 | DisableControlAction(0, 140, true) -- INPUT_MELEE_ATTACK_LIGHT | R
57 | DisableControlAction(0, 141, true) -- INPUT_MELEE_ATTACK_HEAVY | Q
58 | DisableControlAction(0, 142, true) -- INPUT_MELEE_ATTACK_ALTERNATE | LEFT MOUSE BUTTON
59 | DisableControlAction(0, 143, true) -- INPUT_MELEE_BLOCK | SPACEBAR
60 | DisableControlAction(0, 257, true) -- INPUT_ATTACK2 | LEFT MOUSE BUTTON
61 | DisableControlAction(0, 263, true) -- INPUT_MELEE_ATTACK1 | R
62 | DisableControlAction(0, 264, true) -- INPUT_MELEE_ATTACK2 | Q
63 | end
64 |
65 | local function disableCamMovement()
66 | DisableControlAction(0, 1, true) -- INPUT_LOOK_LR | MOUSE RIGHT
67 | DisableControlAction(0, 2, true) -- INPUT_LOOK_UD | MOUSE DOWN
68 | DisableControlAction(0, 3, true) -- INPUT_LOOK_UP_ONLY | (NONE)
69 | DisableControlAction(0, 4, true) -- INPUT_LOOK_DOWN_ONLY | MOUSE DOWN
70 | DisableControlAction(0, 5, true) -- INPUT_LOOK_LEFT_ONLY | (NONE)
71 | DisableControlAction(0, 6, true) -- INPUT_LOOK_RIGHT_ONLY | MOUSE RIGHT
72 | DisableControlAction(0, 12, true) -- INPUT_WEAPON_WHEEL_UD | MOUSE DOWN
73 | DisableControlAction(0, 13, true) -- INPUT_WEAPON_WHEEL_LR | MOUSE RIGHT
74 | DisableControlAction(0, 200, true) -- INPUT_FRONTEND_PAUSE_ALTERNATE | ESC
75 | end
76 |
77 | local function mouseDownListener()
78 | CreateThread(function()
79 | while running do
80 | setCamPosition()
81 |
82 | if IsDisabledControlJustReleased(0, 24) or IsControlJustReleased(0, 24) then
83 | SetMouseCursorSprite(3)
84 | return
85 | end
86 |
87 | Wait(0)
88 | end
89 | end)
90 | end
91 |
92 | local function inputListener()
93 | setCamPosition() -- Set initial cam position, otherwise cam will remain at player until first left click
94 | CreateThread(function()
95 | while running do
96 | SetMouseCursorActiveThisFrame()
97 | disableCamMovement()
98 | disablePlayerMovement()
99 |
100 | if IsDisabledControlJustPressed(0, 24) or IsControlJustPressed(0, 24) then
101 | SetMouseCursorSprite(4)
102 | mouseDownListener()
103 | end
104 |
105 | if IsDisabledControlJustReleased(0, 14) or IsControlJustReleased(0, 14) then
106 | if gRadius + scrollIncrements <= gRadiusMax then
107 | gRadius += scrollIncrements
108 | setCamPosition()
109 | end
110 | elseif IsDisabledControlJustReleased(0, 15) or IsControlJustReleased(0, 15) then
111 | if gRadius - scrollIncrements >= gRadiusMin then
112 | gRadius -= scrollIncrements
113 | setCamPosition()
114 | end
115 | end
116 |
117 | Wait(0)
118 | end
119 | end)
120 | end
121 |
122 | local function instructionalButton(controlId, text)
123 | ScaleformMovieMethodAddParamPlayerNameString(GetControlInstructionalButton(0, controlId, true))
124 | BeginTextCommandScaleformString("STRING")
125 | AddTextComponentSubstringKeyboardDisplay(text)
126 | EndTextCommandScaleformString()
127 | end
128 |
129 | local function showInstructionalButtons()
130 | CreateThread(function()
131 | scaleform = RequestScaleformMovie("instructional_buttons")
132 | while not HasScaleformMovieLoaded(scaleform) do
133 | Wait(0)
134 | end
135 | BeginScaleformMovieMethod(scaleform, "CLEAR_ALL")
136 | EndScaleformMovieMethod()
137 |
138 | BeginScaleformMovieMethod(scaleform, "SET_DATA_SLOT")
139 | ScaleformMovieMethodAddParamInt(1)
140 | instructionalButton(14, "Decrease zoom")
141 | EndScaleformMovieMethod()
142 |
143 | BeginScaleformMovieMethod(scaleform, "SET_DATA_SLOT")
144 | ScaleformMovieMethodAddParamInt(2)
145 | instructionalButton(15, "Increase zoom")
146 | EndScaleformMovieMethod()
147 |
148 | BeginScaleformMovieMethod(scaleform, "DRAW_INSTRUCTIONAL_BUTTONS")
149 | EndScaleformMovieMethod()
150 |
151 | BeginScaleformMovieMethod(scaleform, "SET_BACKGROUND_COLOUR")
152 | ScaleformMovieMethodAddParamInt(0)
153 | ScaleformMovieMethodAddParamInt(0)
154 | ScaleformMovieMethodAddParamInt(0)
155 | ScaleformMovieMethodAddParamInt(80)
156 | EndScaleformMovieMethod()
157 |
158 | while running do
159 | DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255, 0)
160 | Wait(0)
161 | end
162 | end)
163 | end
164 |
165 | ---@param entity integer
166 | ---@param radiusOptions? {initial?: number, min?: number, max?: number, scrollIncrements?: number}
167 | local function startDragCam(entity, radiusOptions)
168 | running = true
169 | gEntity = entity
170 | gRadius = radiusOptions?.initial or 5.0
171 | gRadiusMin = radiusOptions?.min or 2.5
172 | gRadiusMax = radiusOptions?.max or 10.0
173 | scrollIncrements = radiusOptions?.scrollIncrements or 0.5
174 | cam = CreateCam("DEFAULT_SCRIPTED_CAMERA", true)
175 | RenderScriptCams(true, true, 0, true, false)
176 | showInstructionalButtons()
177 | inputListener()
178 | end
179 |
180 | local function stopDragCam()
181 | running = false
182 | RenderScriptCams(false, true, 0, true, false)
183 | DestroyCam(cam, true)
184 | SetScaleformMovieAsNoLongerNeeded(scaleform)
185 | end
186 |
187 | return {
188 | startDragCam = startDragCam,
189 | stopDragCam = stopDragCam
190 | }
191 |
--------------------------------------------------------------------------------
/carcols_gen9.meta:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 | EVehicleModelColorMetallic_normal
7 | POLICE_SCANNER_COLOUR_black
8 | none
9 |
10 |
11 | 223 Anodized Monochrome
12 | vehicle_paint_ramp_fubuki001
13 |
14 | -
15 |
16 | EVehicleModelColorMetallic_normal
17 | POLICE_SCANNER_COLOUR_yellow
18 | none
19 |
20 |
21 | 224 Day Night Flip
22 | vehicle_paint_ramp_fubuki002
23 |
24 | -
25 |
26 | EVehicleModelColorMetallic_normal
27 | POLICE_SCANNER_COLOUR_pink
28 | none
29 |
30 |
31 | 225 Verlierer Flip
32 | vehicle_paint_ramp_fubuki003
33 |
34 | -
35 |
36 | EVehicleModelColorMetallic_normal
37 | POLICE_SCANNER_COLOUR_green
38 | none
39 |
40 |
41 | 226 Anodized Sprunk
42 | vehicle_paint_ramp_fubuki004
43 |
44 | -
45 |
46 | EVehicleModelColorMetallic_normal
47 | POLICE_SCANNER_COLOUR_pink
48 | none
49 |
50 |
51 | 227 Vice City Flip
52 | vehicle_paint_ramp_fubuki005
53 |
54 | -
55 |
56 | EVehicleModelColorMetallic_normal
57 | POLICE_SCANNER_COLOUR_blue
58 | POLICE_SCANNER_PREFIX_dark
59 |
60 |
61 | 228 Synthwave Pearl
62 | vehicle_paint_ramp_fubuki006
63 |
64 | -
65 |
66 | EVehicleModelColorMetallic_normal
67 | POLICE_SCANNER_COLOUR_blue
68 | POLICE_SCANNER_PREFIX_light
69 |
70 |
71 | 229 Seasons Flip
72 | vehicle_paint_ramp_fubuki007
73 |
74 | -
75 |
76 | EVehicleModelColorMetallic_normal
77 | POLICE_SCANNER_COLOUR_yellow
78 | none
79 |
80 |
81 | 230 TBOGT Pearl
82 | vehicle_paint_ramp_fubuki008
83 |
84 | -
85 |
86 | EVehicleModelColorMetallic_normal
87 | POLICE_SCANNER_COLOUR_blue
88 | POLICE_SCANNER_PREFIX_light
89 |
90 |
91 | 231 Bubblegum Pearl
92 | vehicle_paint_ramp_fubuki009
93 |
94 | -
95 |
96 | EVehicleModelColorMetallic_normal
97 | POLICE_SCANNER_COLOUR_red
98 | none
99 |
100 |
101 | 232 Rainbow Prismatic
102 | vehicle_paint_ramp_fubuki010
103 |
104 | -
105 |
106 | EVehicleModelColorMetallic_normal
107 | POLICE_SCANNER_COLOUR_orange
108 | none
109 |
110 |
111 | 233 Sunset Flip
112 | vehicle_paint_ramp_fubuki011
113 |
114 | -
115 |
116 | EVehicleModelColorMetallic_normal
117 | POLICE_SCANNER_COLOUR_blue
118 | none
119 |
120 |
121 | 234 Visions Prismatic
122 | vehicle_paint_ramp_fubuki012
123 |
124 | -
125 |
126 | EVehicleModelColorMetallic_normal
127 | POLICE_SCANNER_COLOUR_brown
128 | none
129 |
130 |
131 | 235 Maziora Prismatic
132 | vehicle_paint_ramp_fubuki013
133 |
134 | -
135 |
136 | EVehicleModelColorMetallic_normal
137 | POLICE_SCANNER_COLOUR_red
138 | POLICE_SCANNER_PREFIX_bright
139 |
140 |
141 | 236 3DGlasses Flip
142 | vehicle_paint_ramp_fubuki014
143 |
144 | -
145 |
146 | EVehicleModelColorMetallic_normal
147 | POLICE_SCANNER_COLOUR_green
148 | none
149 |
150 |
151 | 237 Christmas Flip
152 | vehicle_paint_ramp_fubuki015
153 |
154 | -
155 |
156 | EVehicleModelColorMetallic_normal
157 | POLICE_SCANNER_COLOUR_red
158 | none
159 |
160 |
161 | 238 Temperature Prismatic
162 | vehicle_paint_ramp_fubuki016
163 |
164 | -
165 |
166 | EVehicleModelColorMetallic_normal
167 | POLICE_SCANNER_COLOUR_red
168 | none
169 |
170 |
171 | 239 HSW Flip
172 | vehicle_paint_ramp_fubuki017
173 |
174 | -
175 |
176 | EVehicleModelColorMetallic_normal
177 | POLICE_SCANNER_COLOUR_pink
178 | none
179 |
180 |
181 | 240 Anodized Electro
182 | vehicle_paint_ramp_fubuki018
183 |
184 | -
185 |
186 | EVehicleModelColorMetallic_normal
187 | POLICE_SCANNER_COLOUR_green
188 | none
189 |
190 |
191 | 241 Monika Prismatic
192 | vehicle_paint_ramp_fubuki019
193 |
194 | -
195 |
196 | EVehicleModelColorMetallic_normal
197 | POLICE_SCANNER_COLOUR_blue
198 | POLICE_SCANNER_PREFIX_light
199 |
200 |
201 | 242 Anodized Fubuki
202 | vehicle_paint_ramp_fubuki020
203 |
204 |
205 |
--------------------------------------------------------------------------------
/config.lua:
--------------------------------------------------------------------------------
1 | Config = {}
2 |
3 | Config.Currency = '$'
4 |
5 | -- If you experience issues with your zones not working, please ensure the Z value of your vec3 points match. Using different heights may cause problems.
6 |
7 | ---@type ZoneOptions[]
8 | Config.Zones = {
9 | -- Benny's Example Zone
10 | -- {
11 | -- freeRepair = { 'police' }, -- Provides free repairs to specified job
12 | -- freeMods = { 'police' }, -- provides free modifications to specified job
13 | -- job = { 'police' }, -- Restricts customs access to a specific job (useful for restricting to mechanics, police, ambulance, etc)
14 | -- blipLabel = "EXAMPLE ZONE",
15 | -- blipColor = 5, -- https://docs.fivem.net/docs/game-references/blips/#blip-colors
16 | -- points = {
17 | -- vec3(-344.36, -121.92, 38.60),
18 | -- vec3(-319.43, -130.65, 38.60),
19 | -- vec3(-324.77, -147.93, 38.60),
20 | -- vec3(-348.59, -139.1, 38.60),
21 | -- }
22 | -- },
23 |
24 | -- Default GTA 5 Customs and Benny's Locations
25 | {
26 | blipLabel = "Los Santos Customs - Vinewood",
27 | blipColor = 5,
28 | points = {
29 | vec3(-344.36, -121.92, 38.60),
30 | vec3(-319.43, -130.65, 38.60),
31 | vec3(-324.77, -147.93, 38.60),
32 | vec3(-348.59, -139.1, 38.60),
33 | }
34 | },
35 | {
36 | blipLabel = "Los Santos Customs - Airport",
37 | blipColor = 5,
38 | points = {
39 | vec3(-1147.7, -1990.31, 13.15),
40 | vec3(-1171.05, -2013.96, 13.15),
41 | vec3(-1158.38, -2026.03, 13.15),
42 | vec3(-1139.17, -2007.18, 13.15),
43 | vec3(-1144.73, -1992.89, 13.15),
44 | }
45 | },
46 | {
47 | blipLabel = "Los Santos Customs - East",
48 | blipColor = 5,
49 | points = {
50 | vec3(724.93, -1092.04, 22.15),
51 | vec3(738.52, -1094.83, 22.15),
52 | vec3(737.36, -1064.56, 22.15),
53 | vec3(724.14, -1063.71, 22.15),
54 | }
55 | },
56 | {
57 | blipLabel = "Los Santos Customs - Sandy Shores",
58 | blipColor = 5,
59 | points = {
60 | vec3(1172.12, 2644.76, 38.55),
61 | vec3(1171.39, 2635.66, 38.55),
62 | vec3(1189.77, 2636.08, 38.55),
63 | vec3(1189.74, 2644.07, 38.55),
64 | }
65 | },
66 | {
67 | blipLabel = "Los Santos Customs - Paleto",
68 | blipColor = 5,
69 | points = {
70 | vec3(115.55, 6625.32, 31.75),
71 | vec3(109.19, 6631.69, 31.75),
72 | vec3(97.39, 6620.02, 31.75),
73 | vec3(102.72, 6613.48, 31.75),
74 | }
75 | },
76 | {
77 | blipLabel = "Benny's Motorworks",
78 | blipColor = 5,
79 | points = {
80 | vec3(-203.55, -1311.26, 30.85),
81 | vec3(-228.06, -1319.24, 30.85),
82 | vec3(-228.25, -1334.25, 30.85),
83 | vec3(-214.18, -1341.38, 30.85),
84 | vec3(-195.42, -1321.19, 30.85),
85 | vec3(-195.26, -1314.11, 30.85),
86 | }
87 | },
88 | }
89 |
90 | Config.Mods = {
91 | { id = 0, label = 'Spoiler', category = 'parts' },
92 | { id = 1, label = 'Front Bumper', category = 'parts' },
93 | { id = 2, label = 'Rear Bumper', category = 'parts' },
94 | { id = 3, label = 'Side Skirt', category = 'parts' },
95 | { id = 4, label = 'Exhaust', category = 'parts' },
96 | { id = 5, label = 'Roll Cage', category = 'parts' },
97 | { id = 6, label = 'Grille', category = 'parts' },
98 | { id = 7, label = 'Hood', category = 'parts' },
99 | { id = 8, label = 'Left Fender', category = 'parts' },
100 | { id = 9, label = 'Right Fender', category = 'parts' },
101 | { id = 10, label = 'Roof', category = 'parts' },
102 | { id = 11, label = 'Engine Upgrade', category = 'performance' },
103 | { id = 12, label = 'Brake Upgrade', category = 'performance' },
104 | { id = 13, label = 'Transmission Upgrade', category = 'performance' },
105 | { id = 14, label = 'Horns', category = 'parts' },
106 | { id = 15, label = 'Suspension Upgrade', category = 'performance' },
107 | { id = 16, label = 'Armor Upgrade', category = 'performance', enabled = false },
108 | { id = 17, label = 'Nitrous', category = 'performance', enabled = false },
109 | -- { id = 18, label = 'Turbo Upgrade', category = 'performance' },
110 | { id = 19, label = 'Subwoofer', category = 'parts' },
111 | -- { id = 20, label = 'Tyre smoke', category = 'colors' },
112 | { id = 21, label = 'Hydraulics', category = 'parts' },
113 | -- { id = 22, label = 'Xenon lights', category = 'colors' },
114 | -- { id = 23, label = 'Wheels', category = 'parts' },
115 | -- { id = 24, label = 'Rear wheels or hydraulics', category = 'parts' },
116 | { id = 25, label = 'Plate holder', category = 'parts' },
117 | { id = 26, label = 'Vanity Plates', category = 'parts' },
118 | { id = 27, label = 'Trim A', category = 'parts' },
119 | { id = 28, label = 'Ornaments', category = 'parts' },
120 | { id = 29, label = 'Dashboard', category = 'parts' },
121 | { id = 30, label = 'Dial', category = 'parts' },
122 | { id = 31, label = 'Door Speaker', category = 'parts' },
123 | { id = 32, label = 'Seats', category = 'parts' },
124 | { id = 33, label = 'Steering Wheel', category = 'parts' },
125 | { id = 34, label = 'Shifter Leaver', category = 'parts' },
126 | { id = 35, label = 'Plaque', category = 'parts' },
127 | { id = 36, label = 'Speaker', category = 'parts' },
128 | { id = 37, label = 'Trunk', category = 'parts' },
129 | { id = 38, label = 'Hydraulic', category = 'parts' },
130 | { id = 39, label = 'Engine Block', category = 'parts' },
131 | { id = 40, label = 'Air Filter', category = 'parts' },
132 | { id = 41, label = 'Strut', category = 'parts' },
133 | { id = 42, label = 'Arch Cover', category = 'parts' },
134 | { id = 43, label = 'Aerial', category = 'parts' },
135 | { id = 44, label = 'Trim B', category = 'parts' },
136 | { id = 45, label = 'Fuel Tank', category = 'parts' },
137 | { id = 46, label = 'Left door', category = 'parts' },
138 | { id = 47, label = 'Right door', category = 'parts' },
139 | -- { id = 48, label = 'Livery', category = 'colors' },
140 | { id = 49, label = 'Lightbar', category = 'parts' },
141 | }
142 |
143 | Config.Horns = {
144 | { id = 0, label = 'Truck Horn' },
145 | { id = 1, label = 'Cop Horn' },
146 | { id = 2, label = 'Clown Horn' },
147 | { id = 3, label = 'Musical Horn 1' },
148 | { id = 4, label = 'Musical Horn 2' },
149 | { id = 5, label = 'Musical Horn 3' },
150 | { id = 6, label = 'Musical Horn 4' },
151 | { id = 7, label = 'Musical Horn 5' },
152 | { id = 8, label = 'Sad Trombone' },
153 | { id = 9, label = 'Classical Horn 1' },
154 | { id = 10, label = 'Classical Horn 2' },
155 | { id = 11, label = 'Classical Horn 3' },
156 | { id = 12, label = 'Classical Horn 4' },
157 | { id = 13, label = 'Classical Horn 5' },
158 | { id = 14, label = 'Classical Horn 6' },
159 | { id = 15, label = 'Classical Horn 7' },
160 | { id = 16, label = 'Scale - Do' },
161 | { id = 17, label = 'Scale - Re' },
162 | { id = 18, label = 'Scale - Mi' },
163 | { id = 19, label = 'Scale - Fa' },
164 | { id = 20, label = 'Scale - Sol' },
165 | { id = 21, label = 'Scale - La' },
166 | { id = 22, label = 'Scale - Ti' },
167 | { id = 23, label = 'Scale - Do' },
168 | { id = 24, label = 'Jazz Horn 1' },
169 | { id = 25, label = 'Jazz Horn 2' },
170 | { id = 26, label = 'Jazz Horn 3' },
171 | { id = 27, label = 'Jazz Horn Loop' },
172 | { id = 28, label = 'Star Spangled Banner 1' },
173 | { id = 29, label = 'Star Spangled Banner 2' },
174 | { id = 30, label = 'Star Spangled Banner 3' },
175 | { id = 31, label = 'Star Spangled Banner 4' },
176 | { id = 32, label = 'Classical Horn 8 Loop' },
177 | { id = 33, label = 'Classical Horn 9 Loop' },
178 | { id = 34, label = 'Classical Horn 10 Loop' },
179 | { id = 35, label = 'Classical Horn 8' },
180 | { id = 36, label = 'Classical Horn 9' },
181 | { id = 37, label = 'Classical Horn 10' },
182 | { id = 38, label = 'Funeral Loop' },
183 | { id = 39, label = 'Funeral' },
184 | { id = 40, label = 'Spooky Loop' },
185 | { id = 41, label = 'Spooky' },
186 | { id = 42, label = 'San Andreas Loop' },
187 | { id = 43, label = 'San Andreas' },
188 | { id = 44, label = 'Liberty City Loop' },
189 | { id = 45, label = 'Liberty City' },
190 | { id = 46, label = 'Festive 1 Loop' },
191 | { id = 47, label = 'Festive 1' },
192 | { id = 48, label = 'Festive 2 Loop' },
193 | { id = 49, label = 'Festive 2' },
194 | { id = 50, label = 'Festive 3 Loop' },
195 | { id = 51, label = 'Festive 3' },
196 | { id = 52, label = 'Air Horn Low Loop' },
197 | { id = 53, label = 'Air Horn Low' },
198 | { id = 54, label = 'Air Horn Medium Loop' },
199 | { id = 55, label = 'Air Horn Medium' },
200 | { id = 56, label = 'Air Horn High Loop' },
201 | { id = 57, label = 'Air Horn High' },
202 | }
203 |
204 | -- https://docs.fivem.net/natives/?_0x4F1D4BE3A7F24601
205 | Config.Paints = {
206 | Classic = {
207 | { id = 0, label = 'Black' },
208 | { id = 1, label = 'Graphite' },
209 | { id = 2, label = 'Black Steel' },
210 | { id = 3, label = 'Dark Steel' },
211 | { id = 4, label = 'Silver' },
212 | { id = 5, label = 'Bluish Silver' },
213 | { id = 6, label = 'Rolled Steel' },
214 | { id = 7, label = 'Shadow Silver' },
215 | { id = 8, label = 'Stone Silver' },
216 | { id = 9, label = 'Midnight Silver' },
217 | { id = 10, label = 'Cast Iron Silver' },
218 | { id = 11, label = 'Anhracite Black' },
219 | { id = 27, label = 'Red' },
220 | { id = 28, label = 'Torino Red' },
221 | { id = 29, label = 'Formula Red' },
222 | { id = 30, label = 'Blaze Red' },
223 | { id = 31, label = 'Grace Red' },
224 | { id = 32, label = 'Garnet Red' },
225 | { id = 33, label = 'Sunset Red' },
226 | { id = 34, label = 'Cabernet Red' },
227 | { id = 35, label = 'Candy Red' },
228 | { id = 36, label = 'Sunrise Orange' },
229 | { id = 38, label = 'Orange' },
230 | { id = 49, label = 'Dark Green' },
231 | { id = 50, label = 'Racing Green' },
232 | { id = 51, label = 'Sea Green' },
233 | { id = 52, label = 'Olive Green' },
234 | { id = 53, label = 'Bright Green' },
235 | { id = 54, label = 'Gasoline Green' },
236 | { id = 61, label = 'Galaxy Blue' },
237 | { id = 62, label = 'Dark Blue' },
238 | { id = 63, label = 'Saxon Blue' },
239 | { id = 64, label = 'Blue' },
240 | { id = 65, label = 'Mariner Blue' },
241 | { id = 66, label = 'Harbor Blue' },
242 | { id = 67, label = 'Diamond Blue' },
243 | { id = 68, label = 'Surf Blue' },
244 | { id = 69, label = 'Nautical Blue' },
245 | { id = 70, label = 'Ultra Blue' },
246 | { id = 71, label = 'Schafter Purple' },
247 | { id = 72, label = 'Spinnaker Purple' },
248 | { id = 73, label = 'Racing Blue' },
249 | { id = 74, label = 'Light Blue' },
250 | { id = 88, label = 'Yellow' },
251 | { id = 89, label = 'Race Yellow' },
252 | { id = 90, label = 'Bronze' },
253 | { id = 91, label = 'Dew Yellow' },
254 | { id = 92, label = 'Lime Green' },
255 | { id = 94, label = 'Feltzer Brown' },
256 | { id = 95, label = 'Creeen Brown' },
257 | { id = 96, label = 'Chocolate Brown' },
258 | { id = 97, label = 'Maple Brown' },
259 | { id = 98, label = 'Saddle Brown' },
260 | { id = 99, label = 'Bleached Brown' },
261 | { id = 100, label = 'Moss Brown' },
262 | { id = 101, label = 'Bison Brown' },
263 | { id = 102, label = 'Woodbeech Brown' },
264 | { id = 103, label = 'Beechwood Brown' },
265 | { id = 104, label = 'Sienna Brown' },
266 | { id = 105, label = 'Sandy Brown' },
267 | { id = 106, label = 'Bleached Brown' },
268 | { id = 107, label = 'Cream' },
269 | { id = 111, label = 'Ice White' },
270 | { id = 112, label = 'Frost White' },
271 | { id = 135, label = 'Hot Pink' },
272 | { id = 136, label = 'Salmon Pink' },
273 | { id = 137, label = 'Pfsiter Pink' },
274 | { id = 138, label = 'Bright Orange' },
275 | { id = 141, label = 'Midnight Blue' },
276 | { id = 142, label = 'Midnight Purple' },
277 | { id = 143, label = 'Wine Red' },
278 | { id = 145, label = 'Bright Purple' },
279 | { id = 147, label = 'Carbon Black' },
280 | { id = 150, label = 'Lava Red' },
281 | },
282 | Matte = {
283 | { id = 12, label = 'Black' },
284 | { id = 13, label = 'Gray' },
285 | { id = 14, label = 'Light Gray' },
286 | { id = 39, label = 'Red' },
287 | { id = 40, label = 'Dark Red' },
288 | { id = 41, label = 'Orange' },
289 | { id = 42, label = 'Yellow' },
290 | { id = 55, label = 'Lime Green' },
291 | { id = 82, label = 'Dark Blue' },
292 | { id = 83, label = 'Blue' },
293 | { id = 84, label = 'Midnight Blue' },
294 | { id = 131, label = 'Ice White' },
295 | { id = 148, label = 'Schafter Purple' },
296 | { id = 149, label = 'Midnight Purple' },
297 | { id = 151, label = 'Forest Green' },
298 | { id = 152, label = 'Olive Darb' },
299 | { id = 153, label = 'Dark Earth' },
300 | { id = 154, label = 'Desert Tan' },
301 | { id = 155, label = 'Foliage Green' },
302 | },
303 | Metal = {
304 | { id = 117, label = 'Brushed Steel' },
305 | { id = 118, label = 'Brushed Black Steel' },
306 | { id = 119, label = 'Brushed Aluminum' },
307 | { id = 120, label = 'Chrome', },
308 | { id = 158, label = 'Pure Gold' },
309 | { id = 159, label = 'Brushed Gold' },
310 | },
311 | Worn = {
312 | { id = 21, label = 'Black' },
313 | { id = 22, label = 'Graphite' },
314 | { id = 23, label = 'Silver Grey' },
315 | { id = 24, label = 'Silver' },
316 | { id = 25, label = 'Blue Silver' },
317 | { id = 26, label = 'Shadow Silver' },
318 | { id = 46, label = 'Red' },
319 | { id = 47, label = 'Golden Red' },
320 | { id = 48, label = 'Dark Red' },
321 | { id = 58, label = 'Dark Green' },
322 | { id = 59, label = 'Green' },
323 | { id = 60, label = 'Sea Wash' },
324 | { id = 85, label = 'Dark blue' },
325 | { id = 86, label = 'Blue' },
326 | { id = 87, label = 'Light blue' },
327 | { id = 113, label = 'Honey Beige' },
328 | { id = 114, label = 'Brown' },
329 | { id = 115, label = 'Dark Brown' },
330 | { id = 116, label = 'straw beige' },
331 | { id = 121, label = 'Off White' },
332 | { id = 123, label = 'Orange' },
333 | { id = 124, label = 'Light Orange' },
334 | { id = 126, label = 'Taxi Yellow' },
335 | { id = 130, label = 'Orange' },
336 | { id = 132, label = 'White' },
337 | { id = 133, label = 'Olive Army Green' },
338 | },
339 | Chameleon = {
340 | { id = 161, label = 'Anodized Red Pearl' },
341 | { id = 162, label = 'Anodized Wine Pearl' },
342 | { id = 163, label = 'Anodized Purple Pearl' },
343 | { id = 164, label = 'Anodized Blue Pearl' },
344 | { id = 165, label = 'Anodized Green Pearl' },
345 | { id = 166, label = 'Anodized Lime Pearl' },
346 | { id = 167, label = 'Anodized Copper Pearl' },
347 | { id = 168, label = 'Anodized Bronze Pearl' },
348 | { id = 169, label = 'Anodized Champagne Pearl' },
349 | { id = 170, label = 'Anodized Gold Pearl' },
350 | { id = 171, label = 'Green/Blue Flip' },
351 | { id = 172, label = 'Green/Red Flip' },
352 | { id = 173, label = 'Green/Brown Flip' },
353 | { id = 174, label = 'Green/Turquoise Flip' },
354 | { id = 175, label = 'Green/Purple Flip' },
355 | { id = 176, label = 'Teal/Purple Flip' },
356 | { id = 177, label = 'Turquoise/Red Flip' },
357 | { id = 178, label = 'Turquoise/Purple Flip' },
358 | { id = 179, label = 'Cyan/Purple Flip' },
359 | { id = 180, label = 'Blue/Pink Flip' },
360 | { id = 181, label = 'Blue/Green Flip' },
361 | { id = 182, label = 'Purple/Red Flip' },
362 | { id = 183, label = 'Purple/Green Flip' },
363 | { id = 184, label = 'Magenta/Green Flip' },
364 | { id = 185, label = 'Magenta/Yellow Flip' },
365 | { id = 186, label = 'Burgundy/Green Flip' },
366 | { id = 187, label = 'Magenta/Cyan Flip' },
367 | { id = 188, label = 'Copper/Purple Flip' },
368 | { id = 189, label = 'Magenta/Orange Flip' },
369 | { id = 190, label = 'Red/Orange Flip' },
370 | { id = 191, label = 'Orange/Purple Flip' },
371 | { id = 192, label = 'Orange/Blue Flip' },
372 | { id = 193, label = 'White/Purple Flip' },
373 | { id = 194, label = 'Red/Rainbow Flip' },
374 | { id = 195, label = 'Blue/Rainbow Flip' },
375 | { id = 196, label = 'Dark Green Pearl' },
376 | { id = 197, label = 'Dark Teal Pearl' },
377 | { id = 198, label = 'Dark Blue Pearl' },
378 | { id = 199, label = 'Dark Purple Pearl' },
379 | { id = 200, label = 'Oil Slick Pearl' },
380 | { id = 201, label = 'Light Green Pearl' },
381 | { id = 202, label = 'Light Blue Pearl' },
382 | { id = 203, label = 'Light Purple Pearl' },
383 | { id = 204, label = 'Light Pink Pearl' },
384 | { id = 205, label = 'Off White Pearl' },
385 | { id = 206, label = 'Cute Pink Pearl' },
386 | { id = 207, label = 'Baby Yellow Pearl' },
387 | { id = 208, label = 'Baby Green Pearl' },
388 | { id = 209, label = 'Baby Blue Pearl' },
389 | { id = 210, label = 'Cream Pearl' },
390 | { id = 211, label = 'White Prismatic Pearl' },
391 | { id = 212, label = 'Graphite Prismatic Pearl' },
392 | { id = 213, label = 'Blue Prismatic Pearl' },
393 | { id = 214, label = 'Purple Prismatic Pearl' },
394 | { id = 215, label = 'Hot Pink Prismatic Pearl' },
395 | { id = 216, label = 'Red Prismatic Pearl' },
396 | { id = 217, label = 'Green Prismatic Pearl' },
397 | { id = 218, label = 'Black Prismatic Pearl' },
398 | { id = 219, label = 'Oil Spill Prismatic Pearl' },
399 | { id = 220, label = 'Rainbow Prismatic Pearl' },
400 | { id = 221, label = 'Black Holographic Pearl' },
401 | { id = 222, label = 'White Holographic Pearl' },
402 | { id = 223, label = 'Fubuki-jo Specials: Monochrome' },
403 | { id = 224, label = 'Fubuki-jo Specials: Night & Day' },
404 | { id = 225, label = 'Fubuki-jo Specials: The Verlierer' },
405 | { id = 226, label = 'Fubuki-jo Specials: Sprunk Extreme' },
406 | { id = 227, label = 'Fubuki-jo Specials: Vice City' },
407 | { id = 228, label = 'Fubuki-jo Specials: Synthwave Night' },
408 | { id = 229, label = 'Fubuki-jo Specials: Four Seasons' },
409 | { id = 230, label = 'Fubuki-jo Specials: M9 Throwback' },
410 | { id = 231, label = 'Fubuki-jo Specials: Bubblegum' },
411 | { id = 232, label = 'Fubuki-jo Specials: Full Rainbow' },
412 | { id = 233, label = 'Fubuki-jo Specials: Sunset' },
413 | { id = 234, label = 'Fubuki-jo Specials: The Seven' },
414 | { id = 235, label = 'Fubuki-jo Specials: Kamen Rider' },
415 | { id = 236, label = 'Fubuki-jo Specials: Chromatic' },
416 | { id = 237, label = 'Fubuki-jo Specials: It\'s Christmas!' },
417 | { id = 238, label = 'Fubuki-jo Specials: Temperature' },
418 | { id = 239, label = 'Fubuki-jo Specials: HSW Badge' },
419 | { id = 240, label = 'Fubuki-jo Specials: Anod. Lightning' },
420 | { id = 241, label = 'Fubuki-jo Specials: Emeralds' },
421 | { id = 242, label = 'Fubuki-jo Specials: Fubuki Castle' },
422 | }
423 | }
424 |
425 | Config.ModLabels = {
426 | [11] = { -- Engine
427 | { id = -1, label = 'Stock' },
428 | { id = 0, label = 'Engine 1' },
429 | { id = 1, label = 'Engine 2' },
430 | { id = 2, label = 'Engine 3' },
431 | { id = 3, label = 'Engine 4' },
432 | },
433 | [12] = { -- Brake
434 | { id = -1, label = 'Stock' },
435 | { id = 0, label = 'Brakes 1' },
436 | { id = 1, label = 'Brakes 2' },
437 | { id = 2, label = 'Brakes 3' },
438 | },
439 | [13] = { -- Transmission
440 | { id = -1, label = 'Stock' },
441 | { id = 0, label = 'Transmission 1' },
442 | { id = 1, label = 'Transmission 2' },
443 | { id = 2, label = 'Transmission 3' },
444 | { id = 3, label = 'Transmission 4' },
445 | },
446 | [14] = { -- Horn
447 | { id = 0, label = 'Truck Horn' },
448 | { id = 1, label = 'Cop Horn' },
449 | { id = 2, label = 'Clown Horn' },
450 | { id = 3, label = 'Musical Horn 1' },
451 | { id = 4, label = 'Musical Horn 2' },
452 | { id = 5, label = 'Musical Horn 3' },
453 | { id = 6, label = 'Musical Horn 4' },
454 | { id = 7, label = 'Musical Horn 5' },
455 | { id = 8, label = 'Sad Trombone' },
456 | { id = 9, label = 'Classical Horn 1' },
457 | { id = 10, label = 'Classical Horn 2' },
458 | { id = 11, label = 'Classical Horn 3' },
459 | { id = 12, label = 'Classical Horn 4' },
460 | { id = 13, label = 'Classical Horn 5' },
461 | { id = 14, label = 'Classical Horn 6' },
462 | { id = 15, label = 'Classical Horn 7' },
463 | { id = 16, label = 'Scale - Do' },
464 | { id = 17, label = 'Scale - Re' },
465 | { id = 18, label = 'Scale - Mi' },
466 | { id = 19, label = 'Scale - Fa' },
467 | { id = 20, label = 'Scale - Sol' },
468 | { id = 21, label = 'Scale - La' },
469 | { id = 22, label = 'Scale - Ti' },
470 | { id = 23, label = 'Scale - Do' },
471 | { id = 24, label = 'Jazz Horn 1' },
472 | { id = 25, label = 'Jazz Horn 2' },
473 | { id = 26, label = 'Jazz Horn 3' },
474 | { id = 27, label = 'Jazz Horn Loop' },
475 | { id = 28, label = 'Star Spangled Banner 1' },
476 | { id = 29, label = 'Star Spangled Banner 2' },
477 | { id = 30, label = 'Star Spangled Banner 3' },
478 | { id = 31, label = 'Star Spangled Banner 4' },
479 | { id = 32, label = 'Classical Horn 8 Loop' },
480 | { id = 33, label = 'Classical Horn 9 Loop' },
481 | { id = 34, label = 'Classical Horn 10 Loop' },
482 | { id = 35, label = 'Classical Horn 8' },
483 | { id = 36, label = 'Classical Horn 9' },
484 | { id = 37, label = 'Classical Horn 10' },
485 | { id = 38, label = 'Funeral Loop' },
486 | { id = 39, label = 'Funeral' },
487 | { id = 40, label = 'Spooky Loop' },
488 | { id = 41, label = 'Spooky' },
489 | { id = 42, label = 'San Andreas Loop' },
490 | { id = 43, label = 'San Andreas' },
491 | { id = 44, label = 'Liberty City Loop' },
492 | { id = 45, label = 'Liberty City' },
493 | { id = 46, label = 'Festive 1 Loop' },
494 | { id = 47, label = 'Festive 1' },
495 | { id = 48, label = 'Festive 2 Loop' },
496 | { id = 49, label = 'Festive 2' },
497 | { id = 50, label = 'Festive 3 Loop' },
498 | { id = 51, label = 'Festive 3' },
499 | { id = 52, label = 'Air Horn Low Loop' },
500 | { id = 53, label = 'Air Horn Low' },
501 | { id = 54, label = 'Air Horn Medium Loop' },
502 | { id = 55, label = 'Air Horn Medium' },
503 | { id = 56, label = 'Air Horn High Loop' },
504 | { id = 57, label = 'Air Horn High' },
505 | },
506 | [15] = { -- Suspension
507 | { id = -1, label = 'Stock' },
508 | { id = 0, label = 'Suspension 1' },
509 | { id = 1, label = 'Suspension 2' },
510 | { id = 2, label = 'Suspension 3' },
511 | { id = 3, label = 'Suspension 4' },
512 | { id = 4, label = 'Suspension 5' },
513 | },
514 | [16] = { -- Armor
515 | { id = -1, label = 'Stock' },
516 | { id = 0, label = 'Armor 1' },
517 | { id = 1, label = 'Armor 2' },
518 | { id = 2, label = 'Armor 3' },
519 | { id = 3, label = 'Armor 4' },
520 | { id = 4, label = 'Armor 5' },
521 | },
522 | }
523 |
524 | Config.Xenon = {
525 | { id = -1, label = 'Default' },
526 | { id = 0, label = 'White' },
527 | { id = 1, label = 'Blue' },
528 | { id = 2, label = 'Electric Blue' },
529 | { id = 3, label = 'Mint Green' },
530 | { id = 4, label = 'Lime Green' },
531 | { id = 5, label = 'Yellow' },
532 | { id = 6, label = 'Golden Shower' },
533 | { id = 7, label = 'Orange' },
534 | { id = 8, label = 'Red' },
535 | { id = 9, label = 'Pony Pink' },
536 | { id = 10, label = 'Hot Pink' },
537 | { id = 11, label = 'Purple' },
538 | { id = 12, label = 'Blacklight' },
539 | }
540 |
541 | Config.WindowTints = {
542 | { id = 0, label = 'None' },
543 | { id = 1, label = 'Pure black' },
544 | { id = 2, label = 'Dark Smoke' },
545 | { id = 3, label = 'Light Smoke' },
546 | { id = 4, label = 'Stock' },
547 | { id = 5, label = 'Green' },
548 | }
549 |
550 | Config.PlateIndexes = {
551 | { id = 0, label = 'Blue on White 1' },
552 | { id = 1, label = 'Yellow on Black' },
553 | { id = 2, label = 'Yellow on Blue' },
554 | { id = 3, label = 'Blue on White 2' },
555 | { id = 4, label = 'Blue on White 3' },
556 | { id = 5, label = 'Yankton' },
557 | -- Uncomment below for game build 3095 Chop Shop DLC
558 | --{ id = 6, label = 'eCola' },
559 | --{ id = 7, label = 'Las Venturas' },
560 | --{ id = 8, label = 'Liberty City' },
561 | --{ id = 9, label = 'LS Car Meet' },
562 | --{ id = 10, label = 'Panic' },
563 | --{ id = 11, label = 'Pounders' },
564 | --{ id = 12, label = 'Sprunk' },
565 | }
566 |
567 | Config.Neon = {
568 | { id = 0, label = 'Left' },
569 | { id = 1, label = 'Right' },
570 | { id = 2, label = 'Front' },
571 | { id = 3, label = 'Back' },
572 | }
573 |
574 | Config.NeonColors = {
575 | { label = 'White', r = 222, g = 222, b = 255 },
576 | { label = 'Blue', r = 2, g = 21, b = 255 },
577 | { label = 'Electric Blue', r = 3, g = 83, b = 255 },
578 | { label = 'Mint Green', r = 0, g = 255, b = 140 },
579 | { label = 'Lime Green', r = 94, g = 255, b = 1 },
580 | { label = 'Yellow', r = 255, g = 255, b = 0 },
581 | { label = 'Golden Shower', r = 255, g = 150, b = 0 },
582 | { label = 'Orange', r = 255, g = 62, b = 0 },
583 | { label = 'Red', r = 255, g = 1, b = 1 },
584 | { label = 'Pony Pink', r = 255, g = 50, b = 100 },
585 | { label = 'Hot Pink', r = 255, g = 5, b = 190 },
586 | { label = 'Purple', r = 35, g = 1, b = 255 },
587 | { label = 'Blacklight', r = 15, g = 3, b = 255 },
588 | }
589 |
590 | Config.TyreSmoke = {
591 | { label = "White Smoke", r = 254, g = 254, b = 254 },
592 | { label = "Black Smoke", r = 1, g = 1, b = 1 },
593 | { label = "Blue Smoke", r = 0, g = 150, b = 255 },
594 | { label = "Yellow Smoke", r = 255, g = 255, b = 50 },
595 | { label = "Orange Smoke", r = 255, g = 153, b = 51 },
596 | { label = "Red Smoke", r = 255, g = 10, b = 10 },
597 | { label = "Green Smoke", r = 10, g = 255, b = 10 },
598 | { label = "Purple Smoke", r = 153, g = 10, b = 153 },
599 | { label = "Pink Smoke", r = 255, g = 102, b = 178 },
600 | { label = "Gray Smoke", r = 128, g = 128, b = 128 }
601 | }
602 |
603 | Config.Wheels = {
604 | {
605 | id = 0,
606 | label = 'Sport',
607 | },
608 | {
609 | id = 1,
610 | label = 'Muscle',
611 | },
612 | {
613 | id = 2,
614 | label = 'Lowrider',
615 | },
616 | {
617 | id = 3,
618 | label = 'Suv',
619 | },
620 | {
621 | id = 4,
622 | label = 'Offroad',
623 | },
624 | {
625 | id = 5,
626 | label = 'Tuner',
627 | },
628 | {
629 | id = 6,
630 | label = 'Bike front wheel',
631 | },
632 | {
633 | id = 7,
634 | label = 'Hiend',
635 | },
636 | {
637 | id = 8,
638 | label = "Benny's Original",
639 | },
640 | {
641 | id = 9,
642 | label = "Benny's Bespoke",
643 | },
644 | {
645 | id = 10,
646 | label = 'Open Wheel',
647 | },
648 | {
649 | id = 11,
650 | label = 'Street',
651 | },
652 | {
653 | id = 12,
654 | label = 'Track',
655 | },
656 | }
657 |
658 | Config.Prices = {
659 | ['cosmetic'] = 500,
660 | ['colors'] = 1000,
661 | [11] = { 0, 10000, 20000, 30000, 40000 }, -- Engine
662 | [12] = { 0, 2500, 5000, 7500 }, -- Brakes
663 | [13] = { 0, 5000, 10000, 15000, 20000 }, -- Transmission
664 | [15] = { 0, 3000, 6000, 9000, 12000, 15000 }, -- Suspension
665 | [18] = 10000 -- Turbo
666 | }
667 |
--------------------------------------------------------------------------------