# framework bridge

## Overview

The script uses an editable bridge system to support any framework. All framework-specific functions are located in two files:

* `modules/editable_client.lua` - Client-side functions
* `modules/editable_server.lua` - Server-side functions

{% tabs %}
{% tab title="ESX" %}

#### Client-Side (ESX)

```lua
ESX = exports['es_extended']:getSharedObject()

Menu = {}
Interaction = {}
Notify = {}
Utils = {}

Utils.RequestModel = function(model, timeout)
    return lib.requestModel(model, timeout or 5000)
end

Utils.Callback = function(event, delay, cb, ...)
    lib.callback(event, delay or false, cb, ...)
end

Utils.CreateZone = function(data)
    return lib.zones.box(data)
end

Utils.CreateDUI = function(data)
    return lib.dui:new(data)
end

Utils.CreateBlip = function(data)
    local blip = AddBlipForCoord(data.coords.x, data.coords.y, data.coords.z)
    if data.sprite then SetBlipSprite(blip, data.sprite) end
    SetBlipDisplay(blip, 2)
    if data.scale then SetBlipScale(blip, data.scale) end
    if data.colour then SetBlipColour(blip, data.colour) end
    SetBlipAsShortRange(blip, true)
    if data.name then
        BeginTextCommandSetBlipName("STRING")
        AddTextComponentSubstringPlayerName(data.name)
        EndTextCommandSetBlipName(blip)
    end
    return blip
end

Notify.Success = function(title, description, duration)
    lib.notify({ title = title, description = description, type = 'success', duration = duration or 3000 })
end

Notify.Error = function(title, description, duration)
    lib.notify({ title = title, description = description, type = 'error', duration = duration or 3000 })
end

Notify.Info = function(title, description, duration)
    lib.notify({ title = title, description = description, type = 'info', duration = duration or 3000 })
end

Interaction.Target = function(coords, name, rotation, distance, debug, size, options)
    exports.ox_target:addBoxZone({
        coords = coords, size = size, name = name, rotation = rotation, debug = debug, distance = distance or 2.5, options = options
    })
end

Interaction.TargetObject = function(models, options)
    exports.ox_target:addModel(models, options)
end

Interaction.EntityTarget = function(entity, options)
    exports.ox_target:addLocalEntity(entity, options)
end

Interaction.RemoveTarget = function(name)
    exports.ox_target:removeZone(name)
end

Interaction.RemoveTargetObject = function(models)
    exports.ox_target:removeModel(models)
end

Utils.AddCarKey = function(plate, model, vehicleData)
    print(('[sl-cardealer] AddCarKey called - Plate: %s, Model: %s'):format(plate or 'N/A', model or 'N/A'))
end

Utils.RemoveCarKey = function(plate, model, vehicleData)
    print(('[sl-cardealer] RemoveCarKey called - Plate: %s, Model: %s'):format(plate or 'N/A', model or 'N/A'))
end
```

#### Server-Side (ESX)

```lua
ESX = exports['es_extended']:getSharedObject()

Callback = {}

Callback.Register = function(name, cb)
    lib.callback.register(name, cb)
end

function HasEnoughMoney(source, amount)
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return false end
    return xPlayer.getMoney() >= amount
end

function RemoveMoney(source, amount, reason)
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return false end
    if xPlayer.getMoney() < amount then return false end
    xPlayer.removeMoney(amount)
    return true
end

function CheckMoneyForPurchase(source, amount)
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return {success = false, reason = 'player_not_found', missing = amount} end
    
    local cash = xPlayer.getMoney()
    local bank = xPlayer.getAccount('bank').money

    if cash >= amount then return {success = true, account = 'cash', has = cash} end
    if bank >= amount then return {success = true, account = 'bank', has = bank} end

    return {
        success = false, reason = 'not_enough_money', amount = amount,
        cash = cash, bank = bank, total = cash + bank, missing = amount - (cash + bank),
        missingCash = amount - cash, missingBank = amount - bank
    }
end

function RemoveMoneyForPurchase(source, amount, reason)
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return false end
    
    local cash = xPlayer.getMoney()
    if cash >= amount then
        xPlayer.removeMoney(amount)
        return true, 'cash'
    end
    
    xPlayer.removeAccountMoney('bank', amount)
    return true, 'bank'
end

function AddMoney(source, amount, reason)
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return false end
    xPlayer.addAccountMoney('bank', amount)
    return true
end

function GiveVehicleToPlayer(source, vehicleData, plate)
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return false end
    
    local insertId = MySQL.insert.await('INSERT INTO owned_vehicles (owner, plate, vehicle, type, stored) VALUES (?, ?, ?, ?, ?)', {
        xPlayer.identifier, plate,
        json.encode({model = joaat(vehicleData.spawnName), plate = plate}),
        'car', 1
    })
    
    return insertId ~= nil
end

function CheckIfPlateExists(plate)
    local result = MySQL.single.await('SELECT plate FROM owned_vehicles WHERE plate = ? LIMIT 1', {plate})
    return result ~= nil
end

function GeneratePlate()
    local chars = 'ABCEFGHIJKLMNOPRSTXWYUZ1234567890'
    local plate = ''
    repeat
        plate = ''
        for i = 1, 8 do
            local random = math.random(1, chars:len())
            plate = plate .. chars:sub(random, random)
        end
    until(not CheckIfPlateExists(plate))
    return plate
end
```

{% endtab %}

{% tab title="QB-Core" %}

#### Client-Side (QB-Core)

```lua
QBCore = exports['qb-core']:GetCoreObject()

Menu = {}
Interaction = {}
Notify = {}
Utils = {}

Utils.RequestModel = function(model, timeout)
    return lib.requestModel(model, timeout or 5000)
end

Utils.Callback = function(event, delay, cb, ...)
    lib.callback(event, delay or false, cb, ...)
end

Utils.CreateZone = function(data)
    return lib.zones.box(data)
end

Utils.CreateDUI = function(data)
    return lib.dui:new(data)
end

Utils.CreateBlip = function(data)
    local blip = AddBlipForCoord(data.coords.x, data.coords.y, data.coords.z)
    if data.sprite then SetBlipSprite(blip, data.sprite) end
    SetBlipDisplay(blip, 2)
    if data.scale then SetBlipScale(blip, data.scale) end
    if data.colour then SetBlipColour(blip, data.colour) end
    SetBlipAsShortRange(blip, true)
    if data.name then
        BeginTextCommandSetBlipName("STRING")
        AddTextComponentSubstringPlayerName(data.name)
        EndTextCommandSetBlipName(blip)
    end
    return blip
end

Notify.Success = function(title, description, duration)
    QBCore.Functions.Notify(description, 'success', duration or 3000)
end

Notify.Error = function(title, description, duration)
    QBCore.Functions.Notify(description, 'error', duration or 3000)
end

Notify.Info = function(title, description, duration)
    QBCore.Functions.Notify(description, 'primary', duration or 3000)
end

Interaction.Target = function(coords, name, rotation, distance, debug, size, options)
    exports.ox_target:addBoxZone({
        coords = coords, size = size, name = name, rotation = rotation, debug = debug, distance = distance or 2.5, options = options
    })
end

Interaction.TargetObject = function(models, options)
    exports.ox_target:addModel(models, options)
end

Interaction.EntityTarget = function(entity, options)
    exports.ox_target:addLocalEntity(entity, options)
end

Interaction.RemoveTarget = function(name)
    exports.ox_target:removeZone(name)
end

Interaction.RemoveTargetObject = function(models)
    exports.ox_target:removeModel(models)
end

Utils.AddCarKey = function(plate, model, vehicleData)
    TriggerEvent('vehiclekeys:client:SetOwner', plate)
end

Utils.RemoveCarKey = function(plate, model, vehicleData)
    TriggerServerEvent('qb-vehiclekeys:server:RemoveKey', plate)
end
```

#### Server-Side (QB-Core)

```lua
QBCore = exports['qb-core']:GetCoreObject()

Callback = {}

Callback.Register = function(name, cb)
    lib.callback.register(name, cb)
end

function HasEnoughMoney(source, amount)
    local Player = QBCore.Functions.GetPlayer(source)
    if not Player then return false end
    return Player.PlayerData.money.cash >= amount
end

function RemoveMoney(source, amount, reason)
    local Player = QBCore.Functions.GetPlayer(source)
    if not Player then return false end
    return Player.Functions.RemoveMoney('cash', amount, reason or 'car-dealer')
end

function CheckMoneyForPurchase(source, amount)
    local Player = QBCore.Functions.GetPlayer(source)
    if not Player then return {success = false, reason = 'player_not_found', missing = amount} end
    
    local cash = Player.PlayerData.money.cash
    local bank = Player.PlayerData.money.bank

    if cash >= amount then return {success = true, account = 'cash', has = cash} end
    if bank >= amount then return {success = true, account = 'bank', has = bank} end

    return {
        success = false, reason = 'not_enough_money', amount = amount,
        cash = cash, bank = bank, total = cash + bank, missing = amount - (cash + bank),
        missingCash = amount - cash, missingBank = amount - bank
    }
end

function RemoveMoneyForPurchase(source, amount, reason)
    local Player = QBCore.Functions.GetPlayer(source)
    if not Player then return false end
    
    local cash = Player.PlayerData.money.cash
    if cash >= amount then
        Player.Functions.RemoveMoney('cash', amount, reason or 'vehicle-purchase')
        return true, 'cash'
    end
    
    Player.Functions.RemoveMoney('bank', amount, reason or 'vehicle-purchase')
    return true, 'bank'
end

function AddMoney(source, amount, reason)
    local Player = QBCore.Functions.GetPlayer(source)
    if not Player then return false end
    Player.Functions.AddMoney('bank', amount, reason or 'refund')
    return true
end

function GiveVehicleToPlayer(source, vehicleData, plate)
    local Player = QBCore.Functions.GetPlayer(source)
    if not Player then return false end
    
    local insertId = MySQL.insert.await('INSERT INTO player_vehicles (license, citizenid, vehicle, hash, mods, plate, garage, state) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', {
        Player.PlayerData.license, Player.PlayerData.citizenid,
        vehicleData.spawnName, joaat(vehicleData.spawnName),
        '{}', plate, 'pillboxgarage', 0
    })
    
    return insertId ~= nil
end

function CheckIfPlateExists(plate)
    local result = MySQL.single.await('SELECT plate FROM player_vehicles WHERE plate = ? LIMIT 1', {plate})
    return result ~= nil
end

function GeneratePlate()
    local chars = 'ABCEFGHIJKLMNOPRSTXWYUZ1234567890'
    local plate = ''
    repeat
        plate = ''
        for i = 1, 8 do
            local random = math.random(1, chars:len())
            plate = plate .. chars:sub(random, random)
        end
    until(not CheckIfPlateExists(plate))
    return plate
end
```

{% endtab %}

{% tab title="OX Core" %}

#### Client-Side (OX Core)

```lua
local Ox = require '@ox_core.lib.init'

Menu = {}
Interaction = {}
Notify = {}
Utils = {}

Utils.RequestModel = function(model, timeout)
    return lib.requestModel(model, timeout or 5000)
end

Utils.Callback = function(event, delay, cb, ...)
    lib.callback(event, delay or false, cb, ...)
end

Utils.CreateZone = function(data)
    return lib.zones.box(data)
end

Utils.CreateDUI = function(data)
    return lib.dui:new(data)
end

Utils.CreateBlip = function(data)
    local blip = AddBlipForCoord(data.coords.x, data.coords.y, data.coords.z)
    if data.sprite then SetBlipSprite(blip, data.sprite) end
    SetBlipDisplay(blip, 2)
    if data.scale then SetBlipScale(blip, data.scale) end
    if data.colour then SetBlipColour(blip, data.colour) end
    SetBlipAsShortRange(blip, true)
    if data.name then
        BeginTextCommandSetBlipName("STRING")
        AddTextComponentSubstringPlayerName(data.name)
        EndTextCommandSetBlipName(blip)
    end
    return blip
end

Notify.Success = function(title, description, duration)
    lib.notify({ title = title, description = description, type = 'success', duration = duration or 3000 })
end

Notify.Error = function(title, description, duration)
    lib.notify({ title = title, description = description, type = 'error', duration = duration or 3000 })
end

Notify.Info = function(title, description, duration)
    lib.notify({ title = title, description = description, type = 'info', duration = duration or 3000 })
end

Interaction.Target = function(coords, name, rotation, distance, debug, size, options)
    exports.ox_target:addBoxZone({
        coords = coords, size = size, name = name, rotation = rotation, debug = debug, distance = distance or 2.5, options = options
    })
end

Interaction.TargetObject = function(models, options)
    exports.ox_target:addModel(models, options)
end

Interaction.EntityTarget = function(entity, options)
    exports.ox_target:addLocalEntity(entity, options)
end

Interaction.RemoveTarget = function(name)
    exports.ox_target:removeZone(name)
end

Interaction.RemoveTargetObject = function(models)
    exports.ox_target:removeModel(models)
end

Utils.AddCarKey = function(plate, model, vehicleData)
    print(('[sl-cardealer] AddCarKey called - Plate: %s, Model: %s'):format(plate or 'N/A', model or 'N/A'))
end

Utils.RemoveCarKey = function(plate, model, vehicleData)
    print(('[sl-cardealer] RemoveCarKey called - Plate: %s, Model: %s'):format(plate or 'N/A', model or 'N/A'))
end
```

#### Server-Side (OX Core)

```lua
local Ox = require '@ox_core.lib.init'

Callback = {}

Callback.Register = function(name, cb)
    lib.callback.register(name, cb)
end

function HasEnoughMoney(source, amount)
    local player = Ox.GetPlayer(source)
    if not player then return false end
    
    local charId = player.charId
    local accounts = exports.ox_inventory:GetAccountItemsForPlayer(source) or {}
    
    for _, account in pairs(accounts) do
        if account.name == 'money' and account.count >= amount then
            return true
        end
    end
    
    return false
end

function RemoveMoney(source, amount, reason)
    local success = exports.ox_inventory:RemoveItem(source, 'money', amount)
    return success or false
end

function CheckMoneyForPurchase(source, amount)
    local player = Ox.GetPlayer(source)
    if not player then return {success = false, reason = 'player_not_found', missing = amount} end
    
    local cash = exports.ox_inventory:GetItemCount(source, 'money') or 0
    
    if cash >= amount then
        return {success = true, account = 'cash', has = cash}
    end

    return {
        success = false, reason = 'not_enough_money', amount = amount,
        cash = cash, bank = 0, total = cash, missing = amount - cash,
        missingCash = amount - cash, missingBank = amount
    }
end

function RemoveMoneyForPurchase(source, amount, reason)
    local success = exports.ox_inventory:RemoveItem(source, 'money', amount)
    if success then
        return true, 'cash'
    end
    return false
end

function AddMoney(source, amount, reason)
    local success = exports.ox_inventory:AddItem(source, 'money', amount)
    return success or false
end

function GiveVehicleToPlayer(source, vehicleData, plate)
    local player = Ox.GetPlayer(source)
    if not player then return false end
    
    local insertId = MySQL.insert.await('INSERT INTO vehicles (owner, plate, model, stored) VALUES (?, ?, ?, ?)', {
        player.charId, plate, vehicleData.spawnName, 'impound'
    })
    
    return insertId ~= nil
end

function CheckIfPlateExists(plate)
    local result = MySQL.single.await('SELECT plate FROM vehicles WHERE plate = ? LIMIT 1', {plate})
    return result ~= nil
end

function GeneratePlate()
    local chars = 'ABCEFGHIJKLMNOPRSTXWYUZ1234567890'
    local plate = ''
    repeat
        plate = ''
        for i = 1, 8 do
            local random = math.random(1, chars:len())
            plate = plate .. chars:sub(random, random)
        end
    until(not CheckIfPlateExists(plate))
    return plate
end
```

{% endtab %}

{% tab title="ND Framework" %}

#### Client-Side (ND Framework)

```lua
local NDCore = exports['ND_Core']:GetCoreObject()

Menu = {}
Interaction = {}
Notify = {}
Utils = {}

Utils.RequestModel = function(model, timeout)
    return lib.requestModel(model, timeout or 5000)
end

Utils.Callback = function(event, delay, cb, ...)
    lib.callback(event, delay or false, cb, ...)
end

Utils.CreateZone = function(data)
    return lib.zones.box(data)
end

Utils.CreateDUI = function(data)
    return lib.dui:new(data)
end

Utils.CreateBlip = function(data)
    local blip = AddBlipForCoord(data.coords.x, data.coords.y, data.coords.z)
    if data.sprite then SetBlipSprite(blip, data.sprite) end
    SetBlipDisplay(blip, 2)
    if data.scale then SetBlipScale(blip, data.scale) end
    if data.colour then SetBlipColour(blip, data.colour) end
    SetBlipAsShortRange(blip, true)
    if data.name then
        BeginTextCommandSetBlipName("STRING")
        AddTextComponentSubstringPlayerName(data.name)
        EndTextCommandSetBlipName(blip)
    end
    return blip
end

Notify.Success = function(title, description, duration)
    lib.notify({ title = title, description = description, type = 'success', duration = duration or 3000 })
end

Notify.Error = function(title, description, duration)
    lib.notify({ title = title, description = description, type = 'error', duration = duration or 3000 })
end

Notify.Info = function(title, description, duration)
    lib.notify({ title = title, description = description, type = 'info', duration = duration or 3000 })
end

Interaction.Target = function(coords, name, rotation, distance, debug, size, options)
    exports.ox_target:addBoxZone({
        coords = coords, size = size, name = name, rotation = rotation, debug = debug, distance = distance or 2.5, options = options
    })
end

Interaction.TargetObject = function(models, options)
    exports.ox_target:addModel(models, options)
end

Interaction.EntityTarget = function(entity, options)
    exports.ox_target:addLocalEntity(entity, options)
end

Interaction.RemoveTarget = function(name)
    exports.ox_target:removeZone(name)
end

Interaction.RemoveTargetObject = function(models)
    exports.ox_target:removeModel(models)
end

Utils.AddCarKey = function(plate, model, vehicleData)
    TriggerServerEvent('ND_VehicleSystem:giveKeys', plate)
end

Utils.RemoveCarKey = function(plate, model, vehicleData)
    TriggerServerEvent('ND_VehicleSystem:removeKeys', plate)
end
```

#### Server-Side (ND Framework)

```lua
local NDCore = exports['ND_Core']:GetCoreObject()

Callback = {}

Callback.Register = function(name, cb)
    lib.callback.register(name, cb)
end

function HasEnoughMoney(source, amount)
    local player = NDCore.getPlayer(source)
    if not player then return false end
    return player.cash >= amount
end

function RemoveMoney(source, amount, reason)
    local player = NDCore.getPlayer(source)
    if not player then return false end
    
    if player.cash < amount then return false end
    
    player.deductMoney('cash', amount, reason or 'car-dealer')
    return true
end

function CheckMoneyForPurchase(source, amount)
    local player = NDCore.getPlayer(source)
    if not player then return {success = false, reason = 'player_not_found', missing = amount} end
    
    local cash = player.cash or 0
    local bank = player.bank or 0

    if cash >= amount then return {success = true, account = 'cash', has = cash} end
    if bank >= amount then return {success = true, account = 'bank', has = bank} end

    return {
        success = false, reason = 'not_enough_money', amount = amount,
        cash = cash, bank = bank, total = cash + bank, missing = amount - (cash + bank),
        missingCash = amount - cash, missingBank = amount - bank
    }
end

function RemoveMoneyForPurchase(source, amount, reason)
    local player = NDCore.getPlayer(source)
    if not player then return false end
    
    if player.cash >= amount then
        player.deductMoney('cash', amount, reason or 'vehicle-purchase')
        return true, 'cash'
    end
    
    player.deductMoney('bank', amount, reason or 'vehicle-purchase')
    return true, 'bank'
end

function AddMoney(source, amount, reason)
    local player = NDCore.getPlayer(source)
    if not player then return false end
    player.addMoney('bank', amount, reason or 'refund')
    return true
end

function GiveVehicleToPlayer(source, vehicleData, plate)
    local player = NDCore.getPlayer(source)
    if not player then return false end
    
    local insertId = MySQL.insert.await('INSERT INTO nd_vehicles (owner, plate, vehicle, stored) VALUES (?, ?, ?, ?)', {
        player.id, plate, vehicleData.spawnName, 1
    })
    
    return insertId ~= nil
end

function CheckIfPlateExists(plate)
    local result = MySQL.single.await('SELECT plate FROM nd_vehicles WHERE plate = ? LIMIT 1', {plate})
    return result ~= nil
end

function GeneratePlate()
    local chars = 'ABCEFGHIJKLMNOPRSTXWYUZ1234567890'
    local plate = ''
    repeat
        plate = ''
        for i = 1, 8 do
            local random = math.random(1, chars:len())
            plate = plate .. chars:sub(random, random)
        end
    until(not CheckIfPlateExists(plate))
    return plate
end
```

{% endtab %}
{% endtabs %}

## Car Key Integration

The script provides two functions for vehicle key management that you can customize for your key system.

### Available Key Systems

```lua
Utils.AddCarKey = function(plate, model, vehicleData)
    -- qb-vehiclekeys
    TriggerEvent('vehiclekeys:client:SetOwner', plate)
    
    -- wasabi_carlock
    TriggerServerEvent('wasabi_carlock:GiveKey', plate)
    
    -- qs-vehiclekeys
    TriggerServerEvent('qs-vehiclekeys:server:GiveKey', plate)
    
    -- jaksam vehicle keys
    TriggerServerEvent('vehicle-keys:server:give-key', plate)
end

Utils.RemoveCarKey = function(plate, model, vehicleData)
    -- qb-vehiclekeys
    TriggerServerEvent('qb-vehiclekeys:server:RemoveKey', plate)
    
    -- wasabi_carlock
    TriggerServerEvent('wasabi_carlock:RemoveKey', plate)
    
    -- qs-vehiclekeys
    TriggerServerEvent('qs-vehiclekeys:server:RemoveKey', plate)
end
```

## Function Reference

### Client Functions

| Function                       | Parameters                | Description                |
| ------------------------------ | ------------------------- | -------------------------- |
| Utils.RequestModel             | model, timeout            | Load vehicle model         |
| Utils.Callback                 | event, delay, cb, ...     | Trigger server callback    |
| Utils.CreateZone               | data                      | Create ox\_lib zone        |
| Utils.CreateDUI                | data                      | Create DUI instance        |
| Utils.CreateBlip               | data                      | Create map blip            |
| Utils.AddCarKey                | plate, model, vehicleData | Give car key to player     |
| Utils.RemoveCarKey             | plate, model, vehicleData | Remove car key from player |
| Notify.Success                 | title, desc, duration     | Show success notification  |
| Notify.Error                   | title, desc, duration     | Show error notification    |
| Notify.Info                    | title, desc, duration     | Show info notification     |
| Interaction.Target             | coords, name, ...         | Add target zone            |
| Interaction.RemoveTarget       | name                      | Remove target zone         |
| Interaction.EntityTarget       | entity, options           | Add entity target          |
| Interaction.TargetObject       | models, options           | Add model target           |
| Interaction.RemoveTargetObject | models                    | Remove model target        |

### Server Functions

| Function               | Parameters                 | Returns         | Description               |
| ---------------------- | -------------------------- | --------------- | ------------------------- |
| Callback.Register      | name, cb                   | -               | Register server callback  |
| HasEnoughMoney         | source, amount             | boolean         | Check if player has money |
| RemoveMoney            | source, amount, reason     | boolean         | Remove money from player  |
| CheckMoneyForPurchase  | source, amount             | table           | Check cash and bank       |
| RemoveMoneyForPurchase | source, amount, reason     | boolean, string | Remove from cash or bank  |
| AddMoney               | source, amount, reason     | boolean         | Add money to player       |
| GiveVehicleToPlayer    | source, vehicleData, plate | boolean         | Save vehicle to database  |
| CheckIfPlateExists     | plate                      | boolean         | Check if plate exists     |
| GeneratePlate          | -                          | string          | Generate unique plate     |
