lb-phone


Below is an ESX-ready drop-in replacement for lb-phone:

Path: lb-phone/server/custom/frameworks/esx/vehicles.lua Replace only GetPlayerVehicles and GetVehicle with the versions below.

This version is tailored for izaap_garage using your owned_vehicles schema + your optional impound columns, and it uses your exports to resolve garage/impound labels.

Assumptions (matches your SQL + server logic):

  • ESX table: owned_vehicles

  • Ownership column: owner

  • Plate column: plate

  • Stored column: stored (fallback state)

  • Garage column: parking (fallback garage)

  • Vehicle JSON: mods (fallback vehicle)

  • Optional impound columns: impounded, impound_id, impound_label, impound_reason, impound_release_at, impound_fee, impound_fee_before, impound_fee_after, etc.

-- izaap_garage integration for lb-phone (ESX)
-- Replace GetPlayerVehicles + GetVehicle with this.

local function Trim(s)
    s = tostring(s or "")
    return (s:gsub("^%s*(.-)%s*$", "%1"))
end

local function SafeDecode(v)
    if type(v) == "table" then return v end
    if type(v) ~= "string" then return {} end
    local s = Trim(v)
    if s == "" then return {} end
    local first = s:sub(1, 1)
    if first ~= "{" and first ~= "[" then return {} end
    local ok, obj = pcall(json.decode, s)
    return (ok and type(obj) == "table") and obj or {}
end

local function HasColumn(tableName, columnName)
    local row = MySQL.single.await([[
        SELECT 1 as ok
        FROM information_schema.COLUMNS
        WHERE TABLE_SCHEMA = DATABASE()
          AND TABLE_NAME = ?
          AND COLUMN_NAME = ?
        LIMIT 1
    ]], { tableName, columnName })
    return row ~= nil
end

local ESX_COLS = nil
local function EnsureCols()
    if ESX_COLS then return ESX_COLS end

    ESX_COLS = {
        stored     = HasColumn("owned_vehicles", "stored") and "stored" or (HasColumn("owned_vehicles", "state") and "state" or nil),
        parking    = HasColumn("owned_vehicles", "parking") and "parking" or (HasColumn("owned_vehicles", "garage") and "garage" or nil),
        modsJson   = HasColumn("owned_vehicles", "mods") and "mods" or (HasColumn("owned_vehicles", "vehicle") and "vehicle" or nil),
        vehName    = HasColumn("owned_vehicles", "vehicle") and "vehicle" or nil,

        impounded  = HasColumn("owned_vehicles", "impounded") and "impounded" or nil,
        impound_id = HasColumn("owned_vehicles", "impound_id") and "impound_id" or nil,
        imp_label  = HasColumn("owned_vehicles", "impound_label") and "impound_label" or nil,
        imp_reason = HasColumn("owned_vehicles", "impound_reason") and "impound_reason" or nil,
        imp_rel    = HasColumn("owned_vehicles", "impound_release_at") and "impound_release_at" or nil,
        imp_fee    = HasColumn("owned_vehicles", "impound_fee") and "impound_fee" or nil,
        imp_fee_b  = HasColumn("owned_vehicles", "impound_fee_before") and "impound_fee_before" or nil,
        imp_fee_a  = HasColumn("owned_vehicles", "impound_fee_after") and "impound_fee_after" or nil,
    }

    return ESX_COLS
end

local function GetGarageLabelFromRow(row)
    local cols = EnsureCols()
    if GetResourceState("izaap_garage") ~= "started" then
        return row[cols.parking] or "Garage"
    end

    local g = row[cols.parking]
    if not g or Trim(g) == "" then return "Garage" end

    -- Your export accepts index (1-based) OR label; returning label is fine
    local data = exports["izaap_garage"]:getGarageByIndex(g)
    return (data and data.label) or (data and data.Label) or g
end

local function GetImpoundLabelFromRow(row)
    local cols = EnsureCols()
    if GetResourceState("izaap_garage") ~= "started" then
        return "Impound"
    end

    local label = cols.imp_label and row[cols.imp_label] or nil
    if label and Trim(label) ~= "" then return label end

    local id = cols.impound_id and tonumber(row[cols.impound_id]) or nil
    if id and id > 0 then
        local imp = exports["izaap_garage"]:getImpoundByIndex(id)
        return (imp and imp.label) or (imp and imp.Label) or "Impound"
    end

    return "Impound"
end

local function ReadStatsFromProps(props)
    props = type(props) == "table" and props or {}

    local fuel = tonumber(props.izaap_fuel or props.fuelLevel or props.fuel) -- 0-100
    local eng  = tonumber(props.izaap_engine or props.engineHealth or props.engine) -- 0-1000
    local body = tonumber(props.izaap_body or props.bodyHealth or props.body) -- 0-1000

    local stats = {}

    if fuel then stats.fuel = math.floor(fuel + 0.5) end
    if eng  then stats.engine = math.floor((eng / 10) + 0.5) end
    if body then stats.body   = math.floor((body / 10) + 0.5) end

    return stats
end

function GetPlayerVehicles(source)
    local cols = EnsureCols()

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

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

        local storedRaw = cols.stored and v[cols.stored] or 0
        local storedNum = tonumber(storedRaw) or 0

        local impounded = false
        if cols.impounded then
            impounded = (tonumber(v[cols.impounded]) or 0) == 1
        else
            -- fallback legacy: some schemas use stored==2 for impound
            impounded = storedNum == 2
        end

        local stored = (storedNum == 1) and true or false

        local location = stored and GetGarageLabelFromRow(v) or "out"
        if impounded then
            location = GetImpoundLabelFromRow(v)
        end

        local props = SafeDecode(cols.modsJson and v[cols.modsJson] or nil)
        local stats = ReadStatsFromProps(props)

        local model = props.model
        if model == nil and cols.vehName then
            -- some ESX schemas store spawn name in `vehicle` as string
            local maybe = v[cols.vehName]
            if type(maybe) == "string" and Trim(maybe) ~= "" and not tonumber(maybe) then
                model = maybe
            end
        end

        local impoundReason = nil
        if impounded then
            local reason = cols.imp_reason and v[cols.imp_reason] or nil
            local retrievable = cols.imp_rel and tonumber(v[cols.imp_rel]) or nil

            local price =
                (cols.imp_fee and tonumber(v[cols.imp_fee])) or
                (cols.imp_fee_b and tonumber(v[cols.imp_fee_b])) or
                (cols.imp_fee_a and tonumber(v[cols.imp_fee_a])) or
                nil

            impoundReason = {
                reason = (type(reason) == "string" and Trim(reason) ~= "" and reason) or nil,
                retrievable = retrievable,
                price = price
            }
        end

        toSend[#toSend+1] = {
            plate = v.plate,
            type = nil,
            location = location,
            impounded = impounded,
            statistics = stats,
            impoundReason = impoundReason,
            model = model
        }
    end

    return toSend
end


---------------------------------------------------------------------------------------------

function GetVehicle(source, plate)
    local cols = EnsureCols()

    plate = Trim(plate or "")
    if plate == "" then return end

    -- only allow taking out if stored=1 and not impounded
    local storedCol = cols.stored or "stored"
    local storedCheck = ("`%s` = 1"):format(storedCol)

    if cols.impounded then
        storedCheck = storedCheck .. " AND `impounded` = 0"
    end

    local vehicle = MySQL.single.await(
        ("SELECT * FROM owned_vehicles WHERE owner = ? AND plate = ? AND %s LIMIT 1"):format(storedCheck),
        { GetIdentifier(source), plate }
    )

    if not vehicle then
        return
    end

    -- mark as out
    MySQL.update(
        ("UPDATE owned_vehicles SET `%s` = 0 WHERE plate = ?"):format(storedCol),
        { plate }
    )

    -- return model for phone usage
    local props = SafeDecode(cols.modsJson and vehicle[cols.modsJson] or nil)
    vehicle.model = props.model

    return vehicle
end

Last updated