---@param text string function Framework.Client.ShowTextUI(text) if (Config.DrawText == "auto" and GetResourceState("jg-textui") == "started") or Config.DrawText == "jg-textui" then exports["jg-textui"]:DrawText(text) elseif (Config.DrawText == "auto" and GetResourceState("ox_lib") == "started") or Config.DrawText == "ox_lib" then exports["ox_lib"]:showTextUI(text, { position = "left-center" }) elseif (Config.DrawText == "auto" and GetResourceState("okokTextUI") == "started") or Config.DrawText == "okokTextUI" then exports["okokTextUI"]:Open(text, "lightblue", "left") elseif (Config.DrawText == "auto" and GetResourceState("ps-ui") == "started") or Config.DrawText == "ps-ui" then exports["ps-ui"]:DisplayText(text, "primary") elseif Config.Framework == "QBCore" then exports["qb-core"]:DrawText(text) else error("You do not have a notifications system set up!") end end function Framework.Client.HideTextUI() if (Config.DrawText == "auto" and GetResourceState("jg-textui") == "started") or Config.DrawText == "jg-textui" then exports["jg-textui"]:HideText() elseif (Config.DrawText == "auto" and GetResourceState("ox_lib") == "started") or Config.DrawText == "ox_lib" then exports["ox_lib"]:hideTextUI() elseif (Config.DrawText == "auto" and GetResourceState("okokTextUI") == "started") or Config.DrawText == "okokTextUI" then exports["okokTextUI"]:Close() elseif (Config.DrawText == "auto" and GetResourceState("ps-ui") == "started") or Config.DrawText == "ps-ui" then exports["ps-ui"]:HideText() elseif Config.Framework == "QBCore" then exports["qb-core"]:HideText() else error("You do not have a notifications system set up!") end end ---@param msg string ---@param type? "success" | "warning" | "error" ---@param time? number function Framework.Client.Notify(msg, type, time) type = type or "success" time = time or 5000 if (Config.Notifications == "auto" and GetResourceState("ox_lib") == "started") or Config.Notifications == "ox_lib" then exports["ox_lib"]:notify({ title = "Mechanic", description = msg, type = type }) elseif (Config.Notifications == "auto" and GetResourceState("okokNotify") == "started") or Config.Notifications == "okokNotify" then exports["okokNotify"]:Alert("Mechanic", msg, time, type) elseif (Config.Notifications == "auto" and GetResourceState("ps-ui") == "started") or Config.Notifications == "ps-ui" then exports["ps-ui"]:Notify(msg, type, time) elseif (Config.Notifications == "auto" and GetResourceState("nox_notify") == "started") or Config.Notifications == "nox_notify" then TriggerEvent("nox_notify:showClientNotify", "Mechanic", msg, type, time) else if Config.Framework == "QBCore" then return QBCore.Functions.Notify(msg, type, time) elseif Config.Framework == "Qbox" then exports.qbx_core:Notify(msg, type, time) elseif Config.Framework == "ESX" then return ESX.ShowNotification(msg, type) end end end RegisterNetEvent("jg-mechanic:client:notify", function(...) Framework.Client.Notify(...) end) ---@param toggle boolean function Framework.Client.ToggleHud(toggle) if GetResourceState("jg-hud") == "started" then exports["jg-hud"]:toggleHud(toggle) return end DisplayHud(toggle) DisplayRadar(toggle) end ---@param text string | false ---@param duration? number ---@param cb function Code to run when progress bar completes successfully ---@param cbCancelled? function Code to run if the progress bar is cancelled function Framework.Client.ProgressBar(text, duration, anim, prop, cb, cbCancelled) if Config.ProgressBar == "auto" or Config.ProgressBar == "ox-bar" or Config.ProgressBar == "ox-circle" then if (Config.ProgressBar == "ox-bar" and lib.progressBar or lib.progressCircle)({ duration = duration or 5000, label = text or "", useWhileDead = false, canCancel = true, anim = not anim and {} or { dict = anim.dict, clip = anim.name }, prop = not prop and {} or { model = GetHashKey(prop.model), bone = prop.bone, pos = prop.coords, rot = prop.rotation }, }) then cb() elseif cbCancelled then cbCancelled() end elseif Config.ProgressBar == "qb" then QBCore.Functions.Progressbar(text or "", text, duration, false, true, { disableMovement = false, disableCarMovement = false, disableMouse = false, disableCombat = true, }, not anim and {} or { animDict = anim.dict, anim = anim.name }, not prop and {} or { model = GetHashKey(prop.model), bone = prop.bone, coords = prop.coords, rotation = prop.rotation }, {}, cb(), cbCancelled and cbCancelled() or false) end end ---@param cb function Code to run when skill check is completed successfully ---@param cbFailed function Code to run if the skill check is failed function Framework.Client.SkillCheck(cb, cbFailed) if not Config.UseSkillbars then return Framework.Client.ProgressBar("", Config.ProgressBarDuration or 10000, false, false, cb, cbFailed) end local attempts, maxFails = 0, Config.MaximumSkillCheckAttempts or 3 if Config.SkillCheck == "auto" or Config.SkillCheck == "ox" then repeat success = lib.skillCheck(Config.SkillCheckDifficulty, Config.SkillCheckInputs) if not success then attempts += 1 if attempts == maxFails then return cbFailed() end Framework.Client.Notify(Locale.skillCheckFailed:format(maxFails - attempts), "error") end Wait(success and 100 or 2000) until success return cb() elseif Config.SkillCheck == "qb" and GetResourceState("qb-skillbar") == "started" then local Skillbar = exports["qb-skillbar"]:GetSkillbarObject() Skillbar.Start({ duration = math.random(5000, 10000), pos = math.random(10, 30), width = math.random(10, 20), }, function() cb() end, function() cbFailed() end) else error("Config.SkillCheck is invalid or no skill check resource is available!") end end -- Get a nice vehicle label from either QBCore shared or GTA natives ---@param model string | number function Framework.Client.GetVehicleLabel(model) if type(model) == "string" then if Config.Framework == "QBCore" and QBCore.Shared.Vehicles then local vehShared = QBCore.Shared.Vehicles[model] if vehShared then return vehShared.brand .. " " .. vehShared.name end end if Config.Framework == "Qbox" and exports.qbx_core:GetVehiclesByName() then local vehShared = exports.qbx_core:GetVehiclesByName()[model] if vehShared then return vehShared.brand .. " " .. vehShared.name end end end local hash = type(model) == "string" and joaat(model) or model local makeName = GetMakeNameFromVehicleModel(hash) local modelName = GetDisplayNameFromVehicleModel(hash) local label = GetLabelText(makeName) .. " " .. GetLabelText(modelName) if makeName == "CARNOTFOUND" or modelName == "CARNOTFOUND" then label = tostring(model) else if GetLabelText(modelName) == "NULL" and GetLabelText(makeName) == "NULL" then label = (makeName or "") .. " " .. (modelName or "") elseif GetLabelText(makeName) == "NULL" then label = GetLabelText(modelName) end end return label end ---@param model string | number function Framework.Client.GetVehicleValue(model) local hash = type(model) == "string" and joaat(model) or model -- Base game value (handling file) local vehicleValue = GetVehicleModelValue(hash) if type(model) == "string" then if Config.Framework == "QBCore" and QBCore.Shared.Vehicles then local vehShared = QBCore.Shared.Vehicles[model] if vehShared and vehShared.price then vehicleValue = vehShared.price end end if Config.Framework == "Qbox" and exports.qbx_core:GetVehiclesByName() then local vehShared = exports.qbx_core:GetVehiclesByName()[model] if vehShared and vehShared.price then vehicleValue = vehShared.price end end end -- If you're using jg-dealerships, this would be the most accurate vehicle value if GetResourceState("jg-dealerships") == "started" then local price = lib.callback.await("jg-mechanic:server:dealerships-vehicle-value", false, hash) if price then vehicleValue = price end end return vehicleValue or 50000 -- fallback value of 50000, I guess the average price of a car? end ---@param vehicle integer ---@return string|false plate function Framework.Client.GetPlate(vehicle) local plate = GetVehicleNumberPlateText(vehicle) if not plate or plate == nil or plate == "" then return false end if GetResourceState("brazzers-fakeplates") == "started" then local originalPlate = lib.callback.await("jg-mechanic:server:brazzers-get-plate-from-fakeplate", false, plate) if originalPlate then plate = originalPlate end end local trPlate = string.gsub(plate, "^%s*(.-)%s*$", "%1") return trPlate end ---Add exports for third party deformation & repair scripts ---@param vehicle integer function Framework.Client.RepairVehicle(vehicle) SetVehicleUndriveable(vehicle, false) WashDecalsFromVehicle(vehicle, 1.0) SetVehicleEngineHealth(vehicle, 1000.0) SetVehicleBodyHealth(vehicle, 1000.0) SetVehiclePetrolTankHealth(vehicle, 1000.0) SetVehicleDirtLevel(vehicle, 0.0) SetVehicleDeformationFixed(vehicle) SetVehicleFixed(vehicle) if GetResourceState("VehicleDeformation") == "started" then exports["VehicleDeformation"]:FixVehicleDeformation(vehicle) end --* Add more exports for repair scripts here... feel free to let us know them in Discord so we can add them by default in future updates! end -- -- Player -- function Framework.Client.GetPlayerData() if Config.Framework == "QBCore" then return QBCore.Functions.GetPlayerData() elseif Config.Framework == "Qbox" then return exports.qbx_core:GetPlayerData() elseif Config.Framework == "ESX" then return ESX.GetPlayerData() end end ---@param type "cash" | "bank" | "money" function Framework.Client.GetBalance(type) if Config.Framework == "QBCore" then return QBCore.Functions.GetPlayerData().money[type] elseif Config.Framework == "Qbox" then return exports.qbx_core:GetPlayerData().money[type] elseif Config.Framework == "ESX" then if type == "cash" then type = "money" end for i, acc in pairs(ESX.GetPlayerData().accounts) do if acc.name == type then return acc.money end end return 0 end end ---@return {name: string, label: string, grade: string} | {} function Framework.Client.GetPlayerJob() local player = Framework.Client.GetPlayerData() if not player or not player.job then return {} end if Config.Framework == "QBCore" or Config.Framework == "Qbox" then return { name = player.job.name, label = player.job.label, grade = player.job.grade.level } elseif Config.Framework == "ESX" then return { name = player.job.name, label = player.job.label, grade = player.job.grade } end return {} end ---@param mechanicId string function Framework.Client.GetPlayerJobDuty(mechanicId) if not Config.MechanicLocations[mechanicId] then return false end local onDuty = lib.callback.await("jg-mechanic:server:is-on-duty", false, mechanicId) if Config.Framework == "QBCore" or Config.Framework == "Qbox" then local player = Framework.Client.GetPlayerData() if not player or not player.job then return false end onDuty = player.job.onduty end return onDuty end ---@param toggle boolean function Framework.Client.ToggleJobDuty(toggle) lib.callback.await("jg-mechanic:server:toggle-duty", false, toggle) end -- -- Sound (uses native audio, switch to your preferred sound script if you'd like) -- ---@param soundName string ---@param coords vector3 function Framework.Client.PlaySound(soundName, coords) if Config.DisableSound then return false end lib.requestAudioBank("audiodirectory/jg_mechanic") local soundId = GetSoundId() PlaySoundFromCoord(soundId, soundName, coords.x, coords.y, coords.z, "special_soundset", true, 7.5, false) CreateThread(function() while not HasSoundFinished(soundId) do Wait(1) end ReleaseSoundId(soundId) ReleaseNamedScriptAudioBank("audiodirectory/jg_mechanic") end) return soundId end -- -- Stashes -- ---@param stashId string ---@param stashName string function Framework.Client.OpenInventoryStash(stashId, stashName) if (Config.Inventory == "auto" and GetResourceState("ox_inventory") == "started") or Config.Inventory == "ox_inventory" then exports.ox_inventory:openInventory("stash", stashId) elseif (Config.Inventory == "auto" and GetResourceState("qb-inventory") == "started") or Config.Inventory == "qb-inventory" then TriggerServerEvent("jg-mechanic:server:open-inventory-stash", stashId, stashName) elseif (Config.Inventory == "auto" and GetResourceState("qs-inventory") == "started") or Config.Inventory == "qs-inventory" then exports["qs-inventory"]:RegisterStash(stashName, 50, 50000000) elseif (Config.Inventory == "auto" and GetResourceState("codem-inventory") == "started") or Config.Inventory == "codem-inventory" then TriggerServerEvent("codem-inventory:server:openstash", stashId, 15, 15000, stashName) else error("Error: Invalid inventory system set in config.") end end ---@param soundId integer|false function Framework.Client.StopSound(soundId) if not soundId or Config.DisableSound then return false end if not soundId then return end StopSound(soundId) end