├── 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 | --------------------------------------------------------------------------------