> For the complete documentation index, see [llms.txt](https://docs.otherplanet.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.otherplanet.dev/scripts/op-garages-v3/script-integrations/lb-phone.md).

# lb-phone

lb-phone integration for OP Garages V3.

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

### 1. Server - `GetPlayerVehicles` / `GetVehicle`

Navigate to `lb-phone/server/custom/frameworks/esx/vehicles.lua` and replace the functions below.

#### GetPlayerVehicles

```lua
function GetPlayerVehicles(source)
    local toSend = {}
    local vehicles = MySQL.query.await("SELECT * FROM owned_vehicles WHERE owner = ? ", { GetIdentifier(source) })

    for i = 1, #vehicles do
        local vehicle = vehicles[i] or {}

        if vehicle.stored == nil then
            vehicle.stored = vehicle.state
        end

        local impounded = false

        if type(vehicle.stored) ~= "boolean" then
            impounded = vehicle.stored == 2
            vehicle.stored = vehicle.stored == 1
        end

        if GetResourceState("cd_garage") == "started" then
            debugprint("Using cd_garage")

            impounded = vehicle.in_garage == 2
            vehicle.stored = vehicle.in_garage or vehicle.in_garage == 1
            vehicle.garage = vehicle.garage_id
            vehicle.type = vehicle.garage_type
        elseif GetResourceState("loaf_garage") == "started" then
            vehicle.stored = 1
        elseif GetResourceState("jg-advancedgarages") == "started" then
            debugprint("Using jg-advancedgarages")

            vehicle.stored = vehicle.in_garage
            vehicle.garage = vehicle.garage_id

            impounded = vehicle.impound == 1

            if impounded and vehicle.impound_data then
                vehicle.stored = false
                vehicle.pound = "Impound"

                local impoundInfo = json.decode(vehicle.impound_data)

                vehicle.impoundReason = impoundInfo and {
                    reason = impoundInfo.reason and #impoundInfo.reason > 0 and impoundInfo.reason or nil,
                    retrievable = ConvertJSTimestamp and ConvertJSTimestamp(impoundInfo.retrieval_date) or nil,
                    price = impoundInfo.retrieval_cost,
                    impounder = impoundInfo.charname
                }
            end
        elseif GetResourceState("op-garages") == "started" then
            debugprint("Using op-garages")

            local storedNum = tonumber(vehicle.stored) or 0
            vehicle.stored = storedNum == 0

            local garage = exports['op-garages']:getGarageByIndex(tostring(vehicle.vehicleGarage))
            vehicle.garage = garage and garage.Label or "Garage"

            if tonumber(vehicle.isTowedOut) == 1 then
                local impound = exports['op-garages']:getImpoundByIndex(tostring(vehicle.vehicleImpound))
                vehicle.stored = false
                vehicle.pound = impound and impound.Label or "Impound"
                impounded = true

                local releaseAt = nil
                local rawDate = vehicle.towedToDate
                if type(rawDate) == "number" then
                    releaseAt = math.floor(rawDate > 9999999999 and (rawDate / 1000) or rawDate)
                elseif type(rawDate) == "string" and rawDate ~= "" then
                    local y, m, d, h, mi, s = rawDate:match("^(%d+)%-(%d+)%-(%d+) (%d+):(%d+):(%d+)$")
                    if y then
                        releaseAt = os.time({
                            year = tonumber(y), month = tonumber(m), day = tonumber(d),
                            hour = tonumber(h), min = tonumber(mi), sec = tonumber(s)
                        })
                    end
                end

                local released = releaseAt and os.time() >= releaseAt
                vehicle.impoundReason = {
                    reason = vehicle.impoundReason,
                    retrievable = releaseAt,
                    price = released and vehicle.towedAfterPrice or vehicle.towedPrice,
                    impounder = vehicle.towedOutBy
                }
            end
        end

        if GetResourceState("qs-advancedgarages") == "started" then
            vehicle.stored = vehicle.garage ~= "OUT"
        end

        local location = vehicle.stored and (vehicle.garage or "Garage") or "out"

        if impounded and vehicle.pound then
            location = vehicle.pound
        end

        ---@type VehicleData
        local newCar = {
            plate = vehicle.plate,
            type = vehicle.type,
            location = location,
            impounded = impounded,
            statistics = {},
            impoundReason = vehicle.impoundReason
        }

        if vehicle.damages then
            local damages = json.decode(vehicle.damages)

            if damages?.engineHealth then
                newCar.statistics.engine = math.floor(damages.engineHealth / 10 + 0.5)
            end

            if damages?.bodyHealth then
                newCar.statistics.body = math.floor(damages.bodyHealth / 10 + 0.5)
            end
        end

        local vehicleMods = json.decode(vehicle.vehicle)

        if vehicleMods.fuel then
            newCar.statistics.fuel = math.floor(vehicleMods.fuel + 0.5)
        end

        newCar.model = vehicleMods.model

        toSend[#toSend + 1] = newCar
    end

    return toSend
end
```

#### GetVehicle

```lua
function GetVehicle(source, plate)
    local storedCheck = "`%s` = ?"
    local storedColumn = "stored"

    ---@type any
    local storedValue = 1

    ---@type any
    local outValue = 0

    if GetResourceState("cd_garage") == "started" or GetResourceState("jg-advancedgarages") == "started" then
        storedColumn = "in_garage"
    elseif GetResourceState("qs-advancedgarages") == "started" then
        storedCheck = "`%s` != ?"
        storedColumn = "garage"
        storedValue = "OUT"
        outValue = "OUT"
    elseif GetResourceState("op-garages") == "started" then
        storedValue = 0
        outValue = 1
    end

    storedCheck = storedCheck:format(storedColumn)

    local vehicle = MySQL.single.await(
        "SELECT * FROM owned_vehicles WHERE owner = ? AND plate = ? AND " .. storedCheck,
        { GetIdentifier(source), plate, storedValue }
    )

    if not vehicle then
        return
    end

    MySQL.update(
        "UPDATE owned_vehicles SET `" .. storedColumn .. "` = ? WHERE plate = ?",
        { outValue, plate }
    )

    vehicle.model = json.decode(vehicle.vehicle).model

    return vehicle
end
```

### 2. Client - register valet vehicle in op-garages

Navigate to `lb-phone/client/apps/framework/garage.lua`.

Next to the existing `jg-advancedgarages` register call (after the valet vehicle is spawned), add:

```lua
if GetResourceState("jg-advancedgarages") == "started" then
    TriggerServerEvent("jg-advancedgarages:server:register-vehicle-outside", plate, VehToNet(vehicle))
elseif GetResourceState("op-garages") == "started" then
    TriggerServerEvent("lb-phone:garage:opgaragesOutside", plate, VehToNet(vehicle))
end
```

### 3. Server - op-garages outside register event

Add to `lb-phone/server/apps/framework/garage.lua` (or your ESX vehicles custom file):

```lua
RegisterNetEvent("lb-phone:garage:opgaragesOutside", function(plate, netId)
    local src = source
    if GetResourceState("op-garages") ~= "started" or type(plate) ~= "string" or type(netId) ~= "number" then
        return
    end

    local identifier = GetIdentifier(src)
    if not identifier then return end

    local owned = MySQL.scalar.await(
        "SELECT 1 FROM owned_vehicles WHERE owner = ? AND plate = ? AND `stored` = 1",
        { identifier, plate }
    )
    if not owned then return end

    local vehicle = 0
    for _ = 1, 20 do
        vehicle = NetworkGetEntityFromNetworkId(netId)
        if vehicle ~= 0 and DoesEntityExist(vehicle) then break end
        Wait(100)
    end
    if vehicle == 0 or not DoesEntityExist(vehicle) then return end

    local entityPlate = (GetVehicleNumberPlateText(vehicle) or ""):gsub("%s+", "")
    if entityPlate ~= plate:gsub("%s+", "") then return end

    exports["op-garages"]:setVehicleOutside(plate, netId)
end)
```

{% endtab %}

{% tab title="QB/QBOX" %}

### 1. Server  `GetPlayerVehicles` / `GetVehicle`

Navigate to:

* `lb-phone/server/custom/frameworks/qbox/vehicles.lua`, **or**
* `lb-phone/server/custom/frameworks/qbcore/vehicles.lua`

#### GetPlayerVehicles

```lua
function GetPlayerVehicles(source)
    local toSend = {}
    local vehicles = MySQL.query.await(
        "SELECT * FROM player_vehicles WHERE citizenid = ?",
        { GetIdentifier(source) }
    )

    for i = 1, #vehicles do
        local vehicle = vehicles[i] or {}

        if GetResourceState("cd_garage") == "started" then
            vehicle.state = vehicle.in_garage
            vehicle.garage = vehicle.garage_id
            vehicle.type = vehicle.garage_type
        elseif GetResourceState("loaf_garage") == "started" then
            vehicle.state = 1
        elseif GetResourceState("jg-advancedgarages") == "started" then
            vehicle.state = vehicle.in_garage
            vehicle.garage = vehicle.garage_id

            if vehicle.impound == 1 and vehicle.impound_data then
                vehicle.state = 2
                vehicle.garage = "Impound"

                local impoundInfo = json.decode(vehicle.impound_data)

                vehicle.impoundReason = impoundInfo and {
                    reason = impoundInfo.reason and #impoundInfo.reason > 0 and impoundInfo.reason or nil,
                    retrievable = ConvertJSTimestamp and ConvertJSTimestamp(impoundInfo.retrieval_date) or nil,
                    price = impoundInfo.retrieval_cost,
                    impounder = impoundInfo.charname
                }
            end
        elseif GetResourceState("qs-advancedgarages") == "started" then
            if vehicle.type == "vehicle" then
                vehicle.type = "car"
            end
        elseif GetResourceState("op-garages") == "started" then
            debugprint("Using op-garages")

            vehicle.state = tonumber(vehicle.state) == 0 and 1 or 0

            local garage = exports['op-garages']:getGarageByIndex(tostring(vehicle.vehicleGarage))
            vehicle.garage = garage and garage.Label or "Garage"

            if tonumber(vehicle.isTowedOut) == 1 then
                local impound = exports['op-garages']:getImpoundByIndex(tostring(vehicle.vehicleImpound))
                vehicle.state = 2 
                vehicle.garage = impound and impound.Label or "Impound"

                local releaseAt = nil
                local rawDate = vehicle.towedToDate
                if type(rawDate) == "number" then
                    releaseAt = math.floor(rawDate > 9999999999 and (rawDate / 1000) or rawDate)
                elseif type(rawDate) == "string" and rawDate ~= "" then
                    local y, m, d, h, mi, s = rawDate:match("^(%d+)%-(%d+)%-(%d+) (%d+):(%d+):(%d+)$")
                    if y then
                        releaseAt = os.time({
                            year = tonumber(y), month = tonumber(m), day = tonumber(d),
                            hour = tonumber(h), min = tonumber(mi), sec = tonumber(s)
                        })
                    end
                end

                local released = releaseAt and os.time() >= releaseAt
                vehicle.impoundReason = {
                    reason = vehicle.impoundReason,
                    retrievable = releaseAt,
                    price = released and vehicle.towedAfterPrice or vehicle.towedPrice,
                    impounder = vehicle.towedOutBy
                }
            end
        end

        local location = vehicle.garage or "Garage"

        if vehicle.state == 0 then
            location = "out"
        elseif vehicle.state == 1 or vehicle.state == true then
            location = vehicle.garage or "Garage"
        elseif vehicle.state == 2 then
            location = vehicle.garage or "Impound"
        end

        ---@type VehicleData
        local newCar = {
            plate = vehicle.plate,
            type = QB.Shared.Vehicles[vehicle.hash]?.category or vehicle.type or "car",
            location = location,
            impounded = vehicle.state == 2,
            statistics = {
                engine = math.floor(vehicle.engine / 10 + 0.5),
                body = math.floor(vehicle.body / 10 + 0.5),
                fuel = vehicle.fuel
            },
            impoundReason = vehicle.impoundReason
        }

        newCar.model = tonumber(vehicle.hash) --[[@as number]]

        toSend[#toSend + 1] = newCar
    end

    return toSend
end
```

#### GetVehicle

```lua
function GetVehicle(source, plate)
    local storedColumn = "state"
    local storedValue = 1
    local outValue = 0

    if GetResourceState("cd_garage") == "started" or GetResourceState("jg-advancedgarages") == "started" then
        storedColumn = "in_garage"
    elseif GetResourceState("op-garages") == "started" then
        storedValue = 0
        outValue = 1
    end

    local vehicle = MySQL.single.await(([[
        SELECT plate, mods, `hash`, fuel
        FROM player_vehicles
        WHERE citizenid = ? AND plate = ? AND `%s` = ?
    ]]):format(storedColumn), { GetIdentifier(source), plate, storedValue })

    if not vehicle then
        return
    end

    MySQL.update(
        "UPDATE player_vehicles SET `" .. storedColumn .. "` = ? WHERE plate = ?",
        { outValue, plate }
    )

    vehicle.model = tonumber(vehicle.hash)

    return vehicle
end
```

### 2. Client - register valet vehicle in op-garages

Navigate to `lb-phone/client/apps/framework/garage.lua`.

Next to the existing `jg-advancedgarages` register call (after the valet vehicle is spawned), add:

```lua
if GetResourceState("jg-advancedgarages") == "started" then
    TriggerServerEvent("jg-advancedgarages:server:register-vehicle-outside", plate, VehToNet(vehicle))
elseif GetResourceState("op-garages") == "started" then
    TriggerServerEvent("lb-phone:garage:opgaragesOutside", plate, VehToNet(vehicle))
end
```

### 3. Server - op-garages outside register event

Add to `lb-phone/server/apps/framework/garage.lua` (or your QB/Qbox vehicles custom file):

```lua
RegisterNetEvent("lb-phone:garage:opgaragesOutside", function(plate, netId)
    local src = source
    if GetResourceState("op-garages") ~= "started" or type(plate) ~= "string" or type(netId) ~= "number" then
        return
    end

    local identifier = GetIdentifier(src)
    if not identifier then return end

    local owned = MySQL.scalar.await(
        "SELECT 1 FROM player_vehicles WHERE citizenid = ? AND plate = ? AND state = 1",
        { identifier, plate }
    )
    if not owned then return end

    local vehicle = 0
    for _ = 1, 20 do
        vehicle = NetworkGetEntityFromNetworkId(netId)
        if vehicle ~= 0 and DoesEntityExist(vehicle) then break end
        Wait(100)
    end
    if vehicle == 0 or not DoesEntityExist(vehicle) then return end

    local entityPlate = (GetVehicleNumberPlateText(vehicle) or ""):gsub("%s+", "")
    if entityPlate ~= plate:gsub("%s+", "") then return end

    exports["op-garages"]:setVehicleOutside(plate, netId)
end)
```

{% endtab %}
{% endtabs %}
