> For the complete documentation index, see [llms.txt](https://unknown-development.gitbook.io/unknown-development/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://unknown-development.gitbook.io/unknown-development/scripts/unknown_carloot.md).

# Car Loot

This script supports QBCore, Qbox and ESX

*<mark style="color:$info;">Are you searching for a realistic and rewarding criminal activity for your FiveM server? Look no further! Our Car Loot (Radio Theft) system delivers a modern and immersive stealing experience designed for long-term roleplay.</mark>*

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>Buy yours now</td><td><a href="https://unknown-development.tebex.io/package/carloot">https://unknown-development.tebex.io/package/carloot</a></td></tr><tr><td>Checkout all our scripts</td><td><a href="https://unknown-development.tebex.io/category/resources">https://unknown-development.tebex.io/category/resources</a></td></tr></tbody></table>

## Showcase

{% embed url="<https://www.youtube.com/watch?v=xefq1282Hb4>" %}

## Config

{% tabs %}
{% tab title="config.lua" %}

```lua
Config = {}

-- Framework
Config.Framework = 'qb' -- 'esx', 'qb', 'qbox'

-- Inventory
Config.Inventory = 'ox_inventory' -- 'ox_inventory'

-- Minigame
Config.Minigame = 'ox_lib' -- 'bl_ui', 'devhub_hackingMinigames', 'devhub_minigames', 'ox_lib', 'ps-ui' (DON'T FORGET TO CONFIGURE MINIGAME SETTINGS IN SHARED/MINIGAMES.LUA)

-- Progress bar
Config.ProgressBar = 'ox_lib_bar' -- 'ox_lib_bar', 'ox_lib_circle', 'qb-core', 'unknown_ui' (UNRELEASED...), 'progressBars'

-- Notification
Config.Notify = 'ox_lib' -- 'ox_lib', 'qb-core', 'esx', 'unknown_ui' (UNRELEASED...), 'okokNotify'

-- Dispatch
Config.Dispatch = 'cd_dispatch' -- 'cd_dispatch', 'tk_dispatch', 'rcore_dispatch', 'ps-dispatch' (DON'T FORGET TO CONFIGURE DISPATCH SETTINGS IN SHARED/DISPATCH.LUA)

-- Locale
Config.Locale = 'en' -- 'en', 'lt'

-- Radio loot settings
Config.RadioTypes = {
    ['carradio'] = { min = 1, max = 5, chance = 0.6 },
    ['carradio_cd'] = { min = 2, max = 3, chance = 0.65 },
    ['carradio_lcd'] = { min = 3, max = 5, chance = 0.8 },
}

Config.Tools = {
    ['weapon_crowbar'] = { -- IF YOU WANT TO USE WEAPON_CROWBAR AS A TOOL PLEASE READ OUR DOCUMENTAION: 
        durabilityLoss = 25 -- durability loss per use
    },
    ['screwdriver'] = {
        durabilityLoss = 50 -- durability loss per use
    },
}

Config.Animations = {
    loot = {'mini@repair', 'fixing_a_ped'},
}

Config.LootingDuration = 10000 -- in milliseconds (10 seconds)
Config.noRewardChance = 50 -- Percentage (1 = 1%, 100 = 100%)
Config.UseMinigame = true

-- Police settings
Config.PoliceRequired = 2
Config.PoliceJobName = 'police'
Config.AlertPolice = true
Config.AlertChance = 100 -- Percentage (1 = 1%, 100 = 100%)

Config.Debug = false
```

{% endtab %}

{% tab title="shared/framework.lua" %}

```lua
local Framework = {}

function Framework:GetCurrent()
    if Config.Framework == 'esx' and GetResourceState('es_extended') == 'started' then
        return 'esx'
    elseif Config.Framework == 'qb' and GetResourceState('qb-core') == 'started' then
        return 'qb'
    elseif Config.Framework == 'qbox' and GetResourceState('qbx_core') == 'started' then
        return 'qbox'
    end
    return nil
end

function Framework:RemoveItem(source, itemName, amount)
    if Config.Inventory == 'ox_inventory' then
        return exports.ox_inventory:RemoveItem(source, itemName, amount)
    end
end

function Framework:AddItem(source, itemName, amount)
     if Config.Inventory == 'ox_inventory' then
        return exports.ox_inventory:AddItem(source, itemName, amount)
     end
end

function Framework:SetItemMetadata(source, itemName, slot, metadata)
    if Config.Inventory == 'ox_inventory' then
        return exports.ox_inventory:SetMetadata(source, slot, metadata)
    end
    return false
end

function Framework:CloseInventory()
    if Config.Inventory == 'ox_inventory' then
        return exports.ox_inventory:closeInventory()
    end
end

function Framework:GetPoliceCount()
    local policeCount = 0
    local framework = self:GetCurrent()
    
    if framework == 'qb' then
        local QBCore = exports['qb-core']:GetCoreObject()
        local players = QBCore.Functions.GetPlayers()
        
        for _, playerId in ipairs(players) do
            local Player = QBCore.Functions.GetPlayer(playerId)
            if Player and Player.PlayerData.job and Player.PlayerData.job.name == Config.PoliceJobName then
                policeCount = policeCount + 1
            end
        end
    elseif framework == 'esx' then
        local ESX = exports['es_extended']:getSharedObject()
        local players = ESX.GetPlayers()
        
        for _, playerId in ipairs(players) do
            local xPlayer = ESX.GetPlayerFromId(playerId)
            if xPlayer and xPlayer.job and xPlayer.job.name == Config.PoliceJobName then
                policeCount = policeCount + 1
            end
        end
    elseif framework == 'qbox' then
        local QBCore = exports['qbx_core']:GetCoreObject()
        local players = QBCore.Functions.GetPlayers()
        
        for _, playerId in ipairs(players) do
            local Player = QBCore.Functions.GetPlayer(playerId)
            if Player and Player.PlayerData.job and Player.PlayerData.job.name == Config.PoliceJobName then
                policeCount = policeCount + 1
            end
        end
    end
    
    return policeCount
end

return Framework
```

{% endtab %}

{% tab title="shared/minigames.lua" %}

```lua
function StartMinigame(callback)
    if Config.Minigame == 'ox_lib' and GetResourceState('ox_lib') == 'started' then
        local success = lib.skillCheck({'easy', 'easy', 'easy'}, {'w', 'a', 's', 'd'})
        if callback then callback(success) end
        return success
        
    elseif Config.Minigame == 'bl_ui' and GetResourceState('bl_ui') == 'started' then
        local success = exports['bl_ui']:RapidLines(2, 70, 3)
        if callback then callback(success) end
        return success
        
    elseif Config.Minigame == 'devhub_hackingMinigames' and GetResourceState('devhub_hackingMinigames') == 'started' then
        local success = exports['devhub_hackingMinigames']:startMinigame('morse', 60)
        if callback then callback(success) end
        return success

    elseif Config.Minigame == 'devhub_minigames' and GetResourceState('devhub_minigames') == 'started' then
        local success = exports['devhub_minigames']:startMinigame("minigame_2", "medium")
        if callback then callback(success) end
        return success
    
    elseif Config.Minigame == 'ps-ui' and GetResourceState('ps-ui') == 'started' then
        local success = exports['ps-ui']:Thermite(10, 5, 3) -- Time, Gridsize, IncorrectBlocks
        if callback then callback(success) end
        return success
    end

end
```

{% endtab %}

{% tab title="shared/notify.lua" %}

```lua
function Notify(source, message, type)
    if not source or source == 0 then
        -- Client-side
        if Config.Notify == 'ox_lib' and GetResourceState('ox_lib') == 'started' then
            lib.notify({
                title = 'Car Loot',
                description = message,
                type = type or 'inform'
            })
        elseif Config.Notify == 'qb-core' and GetResourceState('qb-core') == 'started' then
            local QBCore = exports['qb-core']:GetCoreObject()
            if type == 'error' then
                QBCore.Functions.Notify(message, 'error')
            elseif type == 'success' then
                QBCore.Functions.Notify(message, 'success')
            else
                QBCore.Functions.Notify(message, 'primary')
            end
        elseif Config.Notify == 'esx' and GetResourceState('es_extended') == 'started' then
            local ESX = exports['es_extended']:getSharedObject()
            ESX.ShowNotification(message)
        elseif Config.Notify == 'unknown_ui' and GetResourceState('unknown_ui') == 'started' then
            exports['unknown_ui']:showNotify(type, 'Car Loot', message, 5000, "#5f3dc4", "fa-solid fa-car")
        elseif Config.Notify == 'okokNotify' and GetResourceState('okokNotify') == 'started' then
            exports['okokNotify']:Alert('Car Loot', message, 5000, type or 'info', true)
        else
            -- Fallback GTA notification
            BeginTextCommandThefeedPost('STRING')
            AddTextComponentSubstringPlayerName(message)
            EndTextCommandThefeedPostTicker(true, true)
        end
    else
        -- Server-side
        if Config.Notify == 'ox_lib' then
            TriggerClientEvent('ox_lib:notify', source, {
                description = message,
                type = type or 'inform'
            })
        elseif Config.Notify == 'qb-core' then
            TriggerClientEvent('QBCore:Notify', source, message, type or 'primary')
        elseif Config.Notify == 'esx' then
            TriggerClientEvent('esx:showNotification', source, message)
        elseif Config.Notify == 'unknown_ui' then
            TriggerClientEvent('unknown_ui:notify:show', source, type, 'Car Loot', message, 5000, "#5f3dc4", "fa-solid fa-car")
        elseif Config.Notify == 'okokNotify' then
            TriggerClientEvent('okokNotify:Alert', source, 'Car Loot', message, 5000, type or 'info', true)
        end
    end
end
```

{% endtab %}

{% tab title="shared/progressbar.lua" %}

```lua
function ProgressBar(source, label, duration, options)
    options = options or {}
        -- Client-side
        if Config.ProgressBar == 'ox_lib_bar' and GetResourceState('ox_lib') == 'started' then
            if lib.progressBar({
                duration = duration,
                label = label,
                useWhileDead = options.useWhileDead or false,
                canCancel = options.canCancel or true,
                disable = options.disable or {
                    car = true,
                    move = true,
                    combat = true
                },
                anim = options.anim or {},
                onCancel = options.onCancel,
                onFinish = options.onFinish
            }) then
                return true
            else
                if options.onCancel then options.onCancel() end
                return false
            end
        elseif Config.ProgressBar == 'ox_lib_circle' and GetResourceState('ox_lib') == 'started' then
            if lib.progressCircle({
                duration = duration,
                position = 'bottom',
                label = label,
                useWhileDead = options.useWhileDead or false,
                canCancel = options.canCancel or true,
                disable = options.disable or {
                    car = true,
                    move = true,
                    combat = true
                },
                anim = options.anim or {},
                onCancel = options.onCancel,
                onFinish = options.onFinish
            }) then
                return true
            else
                if options.onCancel then options.onCancel() end
                return false
            end
            
        elseif Config.ProgressBar == 'qb-core' and GetResourceState('qb-core') == 'started' then
            local QBCore = exports['qb-core']:GetCoreObject()
            QBCore.Functions.Progressbar('car_looting_progress', label, duration, false, true, {
                disableMovement = true,
                disableCarMovement = true,
                disableMouse = false,
                disableCombat = true,
            }, options.anim or {}, {}, {}, function()
                if options.onFinish then options.onFinish() end
            end, function()
                if options.onCancel then options.onCancel() end
            end)
            return true

        elseif Config.ProgressBar == 'unknown_ui' and GetResourceState('unknown_ui') == 'started' then
            exports['unknown_ui']:showProgress({
                icon = "fa-solid fa-spinner fa-spin",
                duration = duration,
                label = label,
                canCancel = true,
                disable = {
                    move = true,
                    attack = true
                },
                anim = options.anim or {},
            })
            Wait(duration)
            return true
            
        elseif Config.ProgressBar == 'progressBars' and GetResourceState('progressBars') == 'started' then
            exports['progressBars']:startUI(duration, label)
            Wait(duration)
            return true
            
        else
            -- Fallback progress bar
            local startTime = GetGameTimer()
            while GetGameTimer() - startTime < duration do
                Wait(0)
                local elapsed = GetGameTimer() - startTime
                local progress = elapsed / duration
                
                SetTextFont(4)
                SetTextScale(0.5, 0.5)
                SetTextColour(255, 255, 255, 255)
                SetTextOutline()
                BeginTextCommandDisplayText("STRING")
                AddTextComponentSubstringPlayerName(label)
                EndTextCommandDisplayText(0.5, 0.9)
                
                DrawRect(0.5, 0.95, 0.2, 0.03, 0, 0, 0, 150)
                DrawRect(0.5, 0.95, 0.2 * progress, 0.025, 255, 255, 255, 200)
                
                if IsControlJustPressed(0, 200) then -- ESC pressed
                    return false
                end
            end
        return true
    end
end
```

{% endtab %}

{% tab title="shared/dispatch.lua" %}

```lua
function SendPoliceAlert(coords, message)
    if not Config.AlertPolice then 
        return 
    end
    
    local locale = Locales[Config.Locale] or Locales['en']
    local dispatchMessage = message or locale['dispatch_message'] or 'Person spotted stealing radio from car!'
    
    if Config.Dispatch == 'cd_dispatch' and GetResourceState('cd_dispatch') == 'started' then
        TriggerServerEvent('cd_dispatch:AddNotification', {
            job_table = {Config.PoliceJobName or 'police'},
            coords = coords,
            title = '10-45 - Robbery',
            message = dispatchMessage,
            flash = 0,
            uniqueId = 'looting_radio_' .. GetGameTimer(),
            sound = 1,
            blip = {
                sprite = 225,
                scale = 1.0,
                colour = 1,
                flashes = false,
                text = '10-45 - Robbery',
                time = 10,
                radius = 0
            }
        })
        
    elseif Config.Dispatch == 'tk_dispatch' and GetResourceState('tk_dispatch') == 'started' then
        exports['tk_dispatch']:addCall({
            jobs = {Config.PoliceJobName or 'police'},
            coords = coords,
            title = 'Robbery',
            priority = 'Priority 2',
            message = dispatchMessage,
            removeTime = 1000 * 60 * 10,
            code = '10-45',
            blip = {
                sprite = 225,
                color = 3,
                scale = 1.0,
            }
        })
        
    elseif Config.Dispatch == 'ps-dispatch' and GetResourceState('ps-dispatch') == 'started' then
        exports['ps-dispatch']:LootingRadio()

            -- ADD THIS TO YOUR PS-DISPATCH CONFIG (MORE: https://github.com/Project-Sloth/ps-dispatch/blob/main/README.md)

            -- ['lootingRadio'] = { -- Need to match the codeName in alerts.lua
            --     radius = 0,
            --     sprite = 225,
            --     color = 3,
            --     scale = 1.0,
            --     length = 300,
            --     sound = 'Lose_1st',
            --     sound2 = 'GTAO_FM_Events_Soundset',
            --     offset = false,
            --     flash = true
            -- },

            -- ADD THIS TO YOUR PS-DISPATCH CLIENT/ALERTS.LUA

            -- local function LootingRadio()
            --     local coords = GetEntityCoords(cache.ped)
            --     local dispatchData = {
            --         message = 'Person spotted looting radio from car!', -- add this into your locale
            --         codeName = 'lootingRadio', -- this should be the same as in config.lua
            --         code = '10-45',
            --         icon = 'fa-solid fa-box',
            --         priority = 1,
            --         coords = coords,
            --         jobs = { 'leo' },
            --     }

            --     TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
            -- end
            -- exports('LootingRadio', LootingRadio)
        
    elseif Config.Dispatch == 'rcore_dispatch' and GetResourceState('rcore_dispatch') == 'started' then
        local alertData = {
            code = '10-45',                         -- Alert code
            default_priority = 'medium',              -- 'low', 'medium', or 'high'
            coords = coords,                        -- Alert location
            job = Config.PoliceJobName or 'police', -- Job(s) to receive alert (string or table)
            text = dispatchMessage,                 -- Alert description
            type = 'alerts',                        -- Alert type for statistics
            blip_time = 500,                        -- Optional: Blip fade time (seconds)
            -- image = 'url_to_image.jpg',          -- Optional: Image URL
            -- custom_sound = 'url_to_sound.mp3',   -- Optional: Custom sound URL
            blip = {                                -- Optional: Custom blip settings
                sprite = 225,
                colour = 3,
                scale = 1.0,
                text = '10-45 - Robbery',
                flashes = false,
                radius = 0,
            }
        }

        TriggerServerEvent('rcore_dispatch:server:sendAlert', alertData)
        
    else
        Notify(0, dispatchMessage, 'info')
    end
end
```

{% endtab %}
{% endtabs %}

## Locales

{% tabs %}
{% tab title="locales/locales.lua" %}

```lua
Locales = {
    ['en'] = {
        ['must_be_in_vehicle'] = 'You must be in a vehicle!',
        ['already_looted'] = 'This vehicle has already been looted!',
        ['must_be_same_vehicle'] = 'You must be in the same vehicle!',
        ['looting_error'] = 'Error: Could not get looting data',
        ['failed_to_break'] = 'Failed to break the radio.',
        ['looting_radio'] = 'Breaking radio...',
        ['looting_cancelled'] = 'Looting cancelled.',
        ['found_nothing'] = 'Found nothing in the vehicle.',
        ['found_radio'] = 'Found a radio!',
        ['tool_broken'] = 'Your %s broke!',
        ['not_enough_police'] = 'Not enough police officers. Required: %s',
        ['dispatch_message'] = 'Person spotted stealing radio from car!',
    },
    
    ['lt'] = {
        ['must_be_in_vehicle'] = 'Turi būti transporto priemonėje!',
        ['already_looted'] = 'Ši tr. priemonė jau buvo išplėšta!',
        ['must_be_same_vehicle'] = 'Turi būti toje pačioje transporto priemonėje!',
        ['looting_error'] = 'Klaida: Nepavyko gauti looting duomenų',
        ['failed_to_break'] = 'Nepavyko išlaužti magės.',
        ['looting_radio'] = 'Lupama magė...',
        ['looting_cancelled'] = 'Laužimas atšauktas.',
        ['found_nothing'] = 'Mašinoje nieko neradai.',
        ['found_radio'] = 'Radai magių!',
        ['tool_broken'] = 'Tavo %s sulūžo!',
        ['not_enough_police'] = 'Nepakanka pareigūnų. Reikia: %s',
        ['dispatch_message'] = 'Pastebėtas asmuo, vogiantis radiją iš mašinos!',
    }
}
```

{% endtab %}
{% endtabs %}
