RX Scripts Logo
Dealerships

Configurables

All config & open sourced files included within this script.

Config Files

--[[
BY RX Scripts © rxscripts.xyz
--]]

Config = {}

Config.Locale = 'en'
Config.SaveInterval = 10 -- Minutes (Set to 'false' to disable saving on interval, only on server shutdown)
Config.ShowVehicleImages = true -- Show vehicle images in the dealerships menus
Config.VehicleImage = {
    host = '', -- Custom image host URL (e.g., 'https://my-cdn.com/vehicles'). Leave empty to skip. Fetched server-side for security.
    extension = 'png', -- File extension for custom host images (png, webp, jpg, etc.)
}

Config.MoneyTypes = { -- Account names from your framework
    money = 'money',
    bank = 'bank',
}

Config.TestDrive = {
    duration = 120, -- Test drive duration in seconds (2 minutes)
}

Config.Plate = {
    --[[
        Plate Template Configuration
        The plate will be generated using the template below.
        Available placeholders:
        - {LETTERS:n} = n random uppercase letters (e.g., {LETTERS:3} = "ABC")
        - {NUMBERS:n} = n random digits (e.g., {NUMBERS:3} = "123")
        - {CHECK} = 2-digit check number (random 00-99)
        - {RANDOM:n} = n random alphanumeric characters

        Example templates:
        - "{LETTERS:3} {NUMBERS:3}" = "ABC 123" (default)
        - "{LETTERS:2}{NUMBERS:4}" = "AB1234"
        - "{NUMBERS:2}-{LETTERS:3}-{NUMBERS:2}" = "12-ABC-34"
        - "{RANDOM:8}" = "A1B2C3D4"
        - "RX-{NUMBERS:4}" = "RX-1234"
    --]]
    template = "{LETTERS:3} {NUMBERS:3}",
    maxRetries = 100, -- Maximum attempts to generate a unique plate
}

Config.DisplayVehicleSpawnDistance = 200.0 -- Distance in meters to spawn/despawn display vehicles

Config.Finance = {
    intervalMinutes = 60,        -- Real minutes between payments (default 1 hour)
    maxMissedPayments = 3,      -- Missed payments before vehicle is defaulted/repossessed
    repossessOnDefault = true,  -- Delete vehicle from player's garage on default
}

Config.ShowRoomColors = {
    -- Example format: { label = 'ColorName', displayRGB = {r, g, b}, primaryIndex = X, secondaryIndex = Y }
    -- Color indexes reference: https://pastebin.com/pwHci0xK
    { label = 'Red', displayRGB = {200, 25, 25}, primaryIndex = 27, secondaryIndex = 27 },
    { label = 'Pink', displayRGB = {230, 80, 150}, primaryIndex = 137, secondaryIndex = 137 },
    { label = 'Orange', displayRGB = {240, 130, 20}, primaryIndex = 38, secondaryIndex = 38 },
    { label = 'Yellow', displayRGB = {240, 200, 30}, primaryIndex = 88, secondaryIndex = 88 },
    { label = 'Green', displayRGB = {30, 150, 50}, primaryIndex = 92, secondaryIndex = 92 },
    { label = 'Teal', displayRGB = {0, 180, 180}, primaryIndex = 70, secondaryIndex = 70 },
    { label = 'Blue', displayRGB = {30, 80, 200}, primaryIndex = 64, secondaryIndex = 64 },
    { label = 'Purple', displayRGB = {100, 20, 150}, primaryIndex = 145, secondaryIndex = 145 },
    { label = 'White', displayRGB = {255, 255, 255}, primaryIndex = 111, secondaryIndex = 111 },
    { label = 'Silver', displayRGB = {192, 192, 192}, primaryIndex = 4, secondaryIndex = 4 },
    { label = 'Grey', displayRGB = {100, 100, 100}, primaryIndex = 6, secondaryIndex = 6 },
    { label = 'Black', displayRGB = {10, 10, 10}, primaryIndex = 0, secondaryIndex = 0 },
}

--[[
    YOU CAN USE ACE PERMISSIONS TO ALLOW CERTAIN PLAYERS/GROUPS TO ACCESS THE ADMIN DEALERSHIPS PANEL
    EXAMPLE:
        add_ace group.admin dealerships allow
        add_ace identifier.fivem:1432744 dealerships allow #Rejox

    OR YOU CAN USE THE STAFF GROUPS/JOBS BELOW
--]]
Config.AllowedToCreate = {
    jobs = {
        -- 'real_estate_agent',
    },
    groups = {
        'admin',
        'superadmin',
    }
}

Config.Commands = {
    admin = 'dealerships:admin',       -- Opens the admin panel
}

Config.UI = {
    color = {
        primary = { -- Different shades of primary color
            [50] = "#FEDDE9",
            [100] = "#FCBAD3",
            [200] = "#FA76A6",
            [300] = "#F7317A",
            [400] = "#D80955",
            [500] = "#95063B",
            [600] = "#76052E",
            [700] = "#580423",
            [800] = "#3B0217",
            [900] = "#1D010C",
            [950] = "#0F0106"
        },
    }
}

--[[
    ONLY CHANGE THIS PART IF YOU HAVE RENAMED SCRIPTS SUCH AS FRAMEWORK, TARGET, INVENTORY ETC
    RENAME THE SCRIPT NAME TO THE NEW NAME
--]]
---@type table Only change these if you have changed the name of a resource
Resources = {
    FM = { name = 'fmLib', export = 'new' },
    OXTarget = { name = 'ox_target', export = 'all' },
    QBTarget = { name = 'qb-target', export = 'all' },
}
IgnoreScriptFoundLogs = false
ShowDebugPrints = true

Opensource Files

All script-related open source code is contained within these files. Third-party components, including frameworks, inventory systems, and other external code, are separately maintained & open sourced in our fmLib repository.
--[[
BY RX Scripts © rxscripts.xyz
--]]

function GetVehicleCategoryFromClass(classId)
    local classMap = {
        [0] = "compacts",
        [1] = "sedans",
        [2] = "suvs",
        [3] = "coupes",
        [4] = "muscle",
        [5] = "sports classics",
        [6] = "sports",
        [7] = "super",
        [8] = "motorcycles",
        [9] = "off-road",
        [10] = "industrial",
        [11] = "utility",
        [12] = "vans",
        [13] = "cycles",
        [14] = "boats",
        [15] = "helicopters",
        [16] = "planes",
        [17] = "service",
        [18] = "emergency",
        [19] = "military",
        [20] = "commercial",
        [21] = "trains",
        [22] = "open wheel"
    }

    return classMap[classId] or nil
end

function GetVehicleCategoryFromModel(modelName)
    if not modelName or modelName == "" then
        return nil
    end

    local modelHash = joaat(modelName)

    if not IsModelInCdimage(modelHash) or not IsModelAVehicle(modelHash) then
        return nil
    end

    RequestModel(modelHash)
    local timeout = 0
    while not HasModelLoaded(modelHash) and timeout < 500 do
        Wait(10)
        timeout = timeout + 10
    end

    if not HasModelLoaded(modelHash) then
        return nil
    end

    local vehicleClass = GetVehicleClassFromName(modelHash)
    SetModelAsNoLongerNeeded(modelHash)

    return GetVehicleCategoryFromClass(vehicleClass)
end

function ShowMarker(type, coords)
    if type == 'managementMenu' then
        DrawMarker(2, coords, 0, 0, 0, 0, 180.0, 0, 0.3, 0.3, 0.3, 204, 0, 102, 100, false, false, 2, true, false, false, false)
    elseif type == 'serverDealership' then
        DrawMarker(2, coords, 0, 0, 0, 0, 180.0, 0, 0.3, 0.3, 0.3, 52, 152, 219, 100, false, false, 2, true, false, false, false)
    elseif type == 'playerDealership' then
        DrawMarker(2, coords, 0, 0, 0, 0, 180.0, 0, 0.3, 0.3, 0.3, 46, 204, 113, 100, false, false, 2, true, false, false, false)
    elseif type == 'tebexDealership' then
        DrawMarker(2, coords, 0, 0, 0, 0, 180.0, 0, 0.3, 0.3, 0.3, 155, 89, 182, 100, false, false, 2, true, false, false, false)
    end
end

function AddDisplayVehicleTarget(vehicle, vehicleData, dealership)
    if not vehicle or not DoesEntityExist(vehicle) then return end

    local displayId = vehicleData.id
    local label, canInteract = GetDisplayVehicleLabel(vehicleData)
    local icon = canInteract and 'fas fa-shopping-cart' or 'fas fa-ban'

    if OXTarget then
        OXTarget:addLocalEntity(vehicle, {
            {
                label = label,
                name = 'display_vehicle_' .. displayId,
                icon = icon,
                distance = 2.5,
                onSelect = function(data)
                    if canInteract then
                        OnDisplayVehicleInteract(vehicleData, dealership)
                    else
                        FM.utils.notify(label, 'error')
                    end
                end
            }
        })
        DisplayVehicleTargets[displayId] = true

    elseif QBTarget then
        QBTarget:AddTargetEntity(vehicle, {
            options = {
                {
                    label = label,
                    icon = icon,
                    action = function(entity)
                        if canInteract then
                            OnDisplayVehicleInteract(vehicleData, dealership)
                        else
                            FM.utils.notify(label, 'error')
                        end
                    end
                }
            },
            distance = 2.5
        })
        DisplayVehicleTargets[displayId] = true
    end
end

function RemoveDisplayVehicleTarget(displayId)
    local vehicle = SpawnedDisplayVehicles[displayId]
    if not vehicle or not DoesEntityExist(vehicle) then
        DisplayVehicleTargets[displayId] = nil
        return
    end

    if OXTarget then
        OXTarget:removeLocalEntity(vehicle, { 'display_vehicle_' .. displayId })
    elseif QBTarget then
        QBTarget:RemoveTargetEntity(vehicle)
    end

    DisplayVehicleTargets[displayId] = nil
end

function GetShowroomVehicleSpecs(vehicle)
    local model = GetEntityModel(vehicle)
    local class = GetVehicleClassFromName(model)

    local speedMult = 3.6
    local isBikeOrCycle = (class == 8 or class == 13 or class == 14 or class == 15 or class == 16)

    local topSpeed
    if isBikeOrCycle then
        topSpeed = math.ceil(GetVehicleModelEstimatedMaxSpeed(model) * speedMult * 0.85)
    else
        topSpeed = math.ceil(GetVehicleModelEstimatedMaxSpeed(model) * speedMult)
    end

    local baseAccel = GetVehicleModelAcceleration(model)
    local acceleration = baseAccel * 0.95

    local baseBraking = GetVehicleModelMaxBraking(model)
    local braking
    if isBikeOrCycle then
        braking = baseBraking * 0.12
    else
        braking = baseBraking
    end

    local traction = GetVehicleModelMaxTraction(model)

    return {
        topSpeed = topSpeed,
        acceleration = acceleration,
        braking = braking,
        traction = traction,
        seats = GetVehicleMaxNumberOfPassengers(vehicle) + 1,
    }
end

--[[
    EVENTS
--]]

RegisterNetEvent('rxdealerships:onTestDriveStarted', function(dealershipId, vehicleModel)
    -- Called when a player starts a test drive. Use for custom logic.
end)

RegisterNetEvent('rxdealerships:onTestDriveEnded', function(dealershipId)
    -- Called when a player ends a test drive. Use for custom logic.
end)