if Config.Framework ~= "esx" then return end local function DetectGarageSystem() local garageSystems = { { name = "cd_garage", resource = "cd_garage" }, { name = "loaf_garage", resource = "loaf_garage" }, { name = "jg-advancedgarages", resource = "jg-advancedgarages" }, { name = "qs-advancedgarages", resource = "qs-advancedgarages" }, { name = "codem-garage", resource = "codem-garage" }, { name = "qb-garages", resource = "qb-garages" }, -- left for cross-compat { name = "tgiann-real-parking", resource = "tgiann-real-parking" }, { name = "okokGarage", resource = "okokGarage" }, { name = "op-garages", resource = "op-garages" }, } for _, system in ipairs(garageSystems) do if GetResourceState(system.resource) == "started" then DebugPrint("Garage system detected: " .. system.name) return system.name end end return "default" end local function ProcessVehicleData(row, garageSystem) if not row then return nil end local processed = { plate = row.plate, stored = false, location= "out", garage = nil, type = row.type or row.category or nil, -- some schemas store type model = nil, statistics = { engine = 100, body = 100, fuel = 100 } } if row.stored ~= nil then if type(row.stored) == "boolean" then processed.stored = row.stored else processed.stored = (tonumber(row.stored) or 0) == 1 end elseif row.in_garage ~= nil then processed.stored = (row.in_garage == 1) or (row.in_garage == true) elseif row.state ~= nil then processed.stored = (row.state == 1) or (row.state == true) end if garageSystem == "cd_garage" then processed.stored = (row.in_garage == 1) or (row.in_garage == true) or processed.stored processed.garage = row.garage_id or row.garage processed.type = row.garage_type or processed.type elseif garageSystem == "loaf_garage" then processed.stored = true -- loaf marks everything as recallable processed.garage = row.garage or processed.garage elseif garageSystem == "jg-advancedgarages" then processed.stored = (row.in_garage == 1) or (row.in_garage == true) or processed.stored processed.garage = row.garage_id or row.garage elseif garageSystem == "qs-advancedgarages" then processed.stored = (row.garage and row.garage ~= "out") or processed.stored processed.garage = row.garage or processed.garage elseif garageSystem == "codem-garage" then processed.stored = (row.stored == 1) or (row.stored == true) or processed.stored processed.garage = row.parking or row.garage or processed.garage elseif garageSystem == "tgiann-real-parking" then processed.stored = exports["tgiann-realparking"] and exports["tgiann-realparking"]:IsVehicleParkedAnyGarage(row.plate) or false processed.garage = row.garage or '' elseif garageSystem == "qb-garages" then processed.stored = (row.state == 1) or (row.state == true) or processed.stored processed.garage = row.garage or processed.garage elseif garageSystem == "okokGarage" then processed.stored = (row.state == 1) or (row.state == true) or processed.stored processed.garage = row.garage or processed.garage elseif garageSystem == "op-garages" then processed.stored = (row.state == 0 and row.isTowedOut == 0) or (row.state == false and row.isTowedOut == false) or processed.stored processed.garage = row.garage or 'Garage' else processed.garage = row.garage or processed.garage end processed.location = processed.stored and (processed.garage or "garage") or "out" local props if row.vehicle then local ok, decoded = pcall(json.decode, row.vehicle) if ok and decoded then props = decoded end elseif row.mods then local ok, decoded = pcall(json.decode, row.mods) if ok and decoded then props = decoded end end if props then processed.model = props.model or processed.model local eng = row.engine or props.engineHealth or props.healthEngine local bod = row.body or props.bodyHealth or props.healthBody local fuel = row.fuel or props.fuelLevel or props.fuel processed.statistics.engine = eng and math.floor((tonumber(eng) or 1000) / 10 + 0.5) or processed.statistics.engine processed.statistics.body = bod and math.floor((tonumber(bod) or 1000) / 10 + 0.5) or processed.statistics.body processed.statistics.fuel = tonumber(fuel) or processed.statistics.fuel else DebugPrint(("Warning: Could not decode vehicle props for plate %s"):format(tostring(row.plate))) processed.model = processed.model or row.model or row.hash and tonumber(row.hash) or nil processed.statistics = { engine = row.engine and math.floor(row.engine / 10 + 0.5) or 100, body = row.body and math.floor(row.body / 10 + 0.5) or 100, fuel = row.fuel or 100, } end return processed end -- =============== LIST PLAYER VEHICLES (ESX) =============== Core.Functions.GetPlayerVehicles = function(playerId) if not playerId then DebugPrint('Error: playerId is required') return {} end local identifier = Core.Functions.GetIdentifier(playerId) if not identifier then DebugPrint('Error: Could not get player identifier for ID: ' .. tostring(playerId)) return {} end local result = MySQL.query.await("SELECT * FROM owned_vehicles WHERE owner = ?", { identifier }) if not result then DebugPrint('Error: Database query failed for identifier: ' .. identifier) return {} end local garageSystem = DetectGarageSystem() local out = {} for i = 1, #result do local row = result[i] local processed = ProcessVehicleData(row, garageSystem) if processed then out[#out + 1] = processed end end DebugPrint(('Retrieved %d vehicles for player: %s'):format(#out, identifier)) return out end -- =============== TAKE A VEHICLE OUT (ESX) =============== Core.Functions.GetVehicleData = function(playerId, plate) if not playerId then DebugPrint('Error: playerId is required') return {} end local identifier = Core.Functions.GetIdentifier(playerId) if not identifier then DebugPrint('Error: Could not get player identifier for ID: ' .. tostring(playerId)) return {} end local garageSystem = DetectGarageSystem() local selectSql, params = "", {} if garageSystem == "cd_garage" or garageSystem == "jg-advancedgarages" then selectSql = "SELECT plate, vehicle, in_garage, garage_id as garage, stored FROM owned_vehicles WHERE owner = ? AND plate = ? AND in_garage = 1" params = { identifier, plate } elseif garageSystem == "qs-advancedgarages" then selectSql = "SELECT plate, vehicle, garage, stored FROM owned_vehicles WHERE owner = ? AND plate = ? AND garage <> 'out'" params = { identifier, plate } elseif garageSystem == "codem-garage" then selectSql = "SELECT plate, vehicle, stored, parking FROM owned_vehicles WHERE owner = ? AND plate = ? AND stored = 1" params = { identifier, plate } elseif garageSystem == "qb-garages" or garageSystem == "okokGarage" then selectSql = "SELECT plate, vehicle, stored, state FROM owned_vehicles WHERE owner = ? AND plate = ? AND stored = 1" params = { identifier, plate } elseif garageSystem == "op-garages" then selectSql = "SELECT plate, vehicle, state, garage FROM owned_vehicles WHERE owner = ? AND plate = ? AND state = 0" params = { identifier, plate } else selectSql = "SELECT plate, vehicle, stored FROM owned_vehicles WHERE owner = ? AND plate = ? AND stored = 1" params = { identifier, plate } end local row = MySQL.single.await(selectSql, params) if not row then DebugPrint('Error: No vehicle found IN GARAGE for plate: ' .. plate) return end local updateSql, updateParams = "", {} if garageSystem == "cd_garage" or garageSystem == "jg-advancedgarages" then updateSql = "UPDATE owned_vehicles SET in_garage = 0 WHERE plate = ?" updateParams = { plate } elseif garageSystem == "qs-advancedgarages" then updateSql = "UPDATE owned_vehicles SET garage = 'out' WHERE plate = ?" updateParams = { plate } elseif garageSystem == "codem-garage" or garageSystem == "qb-garages" or garageSystem == "okokGarage" then updateSql = "UPDATE owned_vehicles SET stored = 0 WHERE plate = ?" updateParams = { plate } elseif garageSystem == "op-garages" then updateSql = "UPDATE owned_vehicles SET state = 1 WHERE plate = ?" updateParams = { plate } else updateSql = "UPDATE owned_vehicles SET stored = 0 WHERE plate = ?" updateParams = { plate } end local changed = MySQL.update.await(updateSql, updateParams) if not changed or changed == 0 then DebugPrint('Error: Database update failed for plate: ' .. plate) return end local vehicleData = { plate = row.plate, mods = row.vehicle, hash = nil, fuel = 100, model = nil } if row.vehicle then local ok, props = pcall(json.decode, row.vehicle) if ok and props then vehicleData.model = props.model and tonumber(props.model) or nil vehicleData.hash = vehicleData.model vehicleData.fuel = props.fuelLevel or props.fuel or 100 vehicleData.mods = row.vehicle end end -- Fallback values if not found in vehicle JSON if not vehicleData.model and row.hash then vehicleData.model = tonumber(row.hash) vehicleData.hash = vehicleData.model end return vehicleData end local function WaitForEntity(entity) local timer = GetGameTimer() + 5000 while not DoesEntityExist(entity) and timer > GetGameTimer() do Wait(0) end return DoesEntityExist(entity) end function Core.Functions.CreateServerVehicle(model, coords, heading) heading = heading or 0 local vehicle = CreateVehicle(model, coords.x, coords.y, coords.z, heading, true, true) if not WaitForEntity(vehicle) then return end SetEntityIgnoreRequestControlFilter(vehicle, true) return vehicle end function Core.Functions.CreateServerPed(model, coords, heading) heading = heading or 0 local ped = CreatePed(4, model, coords.x, coords.y, coords.z, heading, true, true) if not WaitForEntity(ped) then return end SetEntityIgnoreRequestControlFilter(ped, true) return ped end