2026-04-14 17:41:39 +02:00

1185 lines
40 KiB
Lua

local vehicleList = {}
-- preGarageCoords est maintenant global dans main.lua
local menuCamera = nil
local currentGarage = nil
local garageEventListener = nil
local previewVehicle = nil
local vehicleCamera = nil
local dofLoopActive = false
TempVehicle = nil
IsCameraMoving = false
InGarageMenu = false
IsCameraAvailable = true
RegisterNUICallback("existAvailableSlot", function(data, cb)
local garageId = data.garage or currentGarage
local usedSlots = lib.callback.await("advancedgarages:getGarageSlots", false, garageId)
local garageConfig = Config.Garages[garageId]
if not garageConfig then
return cb(false)
end
local garageType = garageConfig.type
local shellId = garageConfig.shell.shell
local totalSlots = #Config.VehicleShowRooms[garageType][shellId].vehicleCoords
if usedSlots >= totalSlots then
return cb(false)
end
cb(true)
end)
function CheckJob(jobName, minGrade)
local playerJob = GetJobName()
local playerGang = GetGang()
local playerGrade = GetJobGrade()
local gradeOk = true
if minGrade then
gradeOk = minGrade <= playerGrade
end
if jobName == playerJob and gradeOk then
return true
end
local gangName = playerGang and playerGang.name
if gangName == jobName and gradeOk then
return true
end
return false
end
function CheckGarageAuthorization(allowedJobs, allowedGangs)
if not allowedJobs and not allowedGangs then
return true
end
local merged = table.merge(allowedJobs or {}, allowedGangs or {})
if #merged == 0 then
return true
end
local playerJob = GetJobName()
local playerGrade = GetJobGrade()
local playerGang = GetGang()
for _, entry in pairs(merged) do
if entry.name == playerJob then
if entry.grades then
if not table.contains(entry.grades, playerGrade) then
goto continue
end
end
return true, entry.name
else
if entry.name == playerGang.name then
if entry.grades then
if not table.contains(entry.grades, playerGang.grade) then
goto continue
end
end
return true, entry.name
end
end
::continue::
end
return false, nil
end
RegisterNUICallback("getPlayerList", function(data, cb)
local players = {}
local allNames = lib.callback.await("advancedgarages:GetPlayersName", false)
local myServerId = GetPlayerServerId(PlayerId())
for serverId, playerName in pairs(allNames) do
if serverId ~= myServerId then
table.insert(players, { id = serverId, name = playerName })
end
end
cb(players)
end)
RegisterNUICallback("getOutVehiclesList", function(data, cb)
local rawList = lib.callback.await("advancedgarages:getOutVehicles", false, currentGarage)
local result = {}
for _, entry in pairs(rawList) do
local vehicleData = json.decode(entry.vehicle)
local modelName = vehicleData.model
local brand = Config.VehicleBrands.brands[modelName]
brand = brand and brand.brand or "Unknown"
result[#result + 1] = {
id = entry.plate,
name = GetVehicleNameFromModel(modelName),
brand = brand
}
end
cb(result)
end)
RegisterNUICallback("getGarageList", function(data, cb)
local garages = {}
local myId = GetPlayerIdentifier()
for garageId, garageData in pairs(Config.Garages) do
local isOwned = not garageData.owner
if isOwned ~= myId and garageId ~= ClosestGarage and garageData.type == nearbyGarageType then
garages[#garages + 1] = garageId
end
end
cb(garages)
end)
local function EquipFists()
SetCurrentPedWeapon(cache.ped, -1569615261, true)
end
RegisterNUICallback("toggle_ped_visibility", function(visible, cb)
cb(1)
if not currentlyInGarage then return end
if visible then
SetEntityAlpha(cache.ped, 255, false)
else
SetEntityAlpha(cache.ped, 0, false)
end
end)
local function StartDofLoop()
dofLoopActive = true
CreateThread(function()
local lastCam = nil
while dofLoopActive do
local cam = GetRenderingCam()
if cam ~= lastCam then
lastCam = cam
SetCamUseShallowDofMode(cam, true)
SetCamNearDof(cam, 0.7)
SetCamFarDof(cam, 5.0)
SetCamDofStrength(cam, 1.0)
end
SetUseHiDof()
Wait(0)
end
end)
end
local function FreezePlayerAtGarage(vehicle)
local ped = cache.ped
IsCameraAvailable = true
inPlayerAnim = true
IsCameraMoving = true
FreezeEntityPosition(ped, false)
StartIdleCam(vehicle, 1)
if vehicle ~= 0 then
TaskEnterVehicle(ped, vehicle, 3000, -1, 1.0, 0)
end
end
function OpenGarageMenu(garageId, isImpound, garageVehicles, isBoat)
if isImpound then
TriggerServerEvent("advancedgarages:server:sentToImpound")
Wait(500)
end
local garageConfig = Config.Garages[garageId] or Config.JobGarages[garageId]
if not garageConfig then
return Notification(i18n.t("garage_not_found"), "error")
end
if not garageVehicles then
garageVehicles = lib.callback.await("advancedgarages:getGarageData", false, garageId)
end
if (not garageVehicles or (isImpound and #garageVehicles == 0)) then
return Notification(i18n.t("garage_empty"), "error")
end
TriggerServerEvent("advancedgarages:RoutePlayer", garageId)
currentlyInGarage = garageId
local jobGarageConfig = Config.JobGarages[garageId]
if garageVehicles then
table.sort(garageVehicles, function(a, b)
return (a.favorite or 0) > (b.favorite or 0)
end)
end
currentGarage = garageId
menuCamera = nil
vehicleList = garageVehicles
ToggleHud(false)
Debug("Named garage activated:", json.encode(currentGarage))
Debug("Additional information of the current garage:", json.encode(Config.Garages[currentGarage], { indent = true }))
TriggerEvent("advancedgarages:GetWeatherSync", true, 14)
DoScreenFadeOut(300)
Wait(500)
preGarageCoords = GetEntityCoords(cache.ped)
vehicleCamera = nil
local garageType = garageConfig and garageConfig.type
if Config.Garages[currentGarage] then
if garageType == "vehicle" then
vehicleCamera = garageConfig.vehicleCamera or Config.vehicleCamera
elseif garageType == "boat" then
if isBoat and not garageConfig.vehicleCamera then
vehicleCamera = Config.BoatCamera
else
vehicleCamera = garageConfig.vehicleCamera
end
elseif garageType == "plane" then
vehicleCamera = garageConfig.vehicleCamera or Config.PlaneCamera
end
else
vehicleCamera = jobGarageConfig and (jobGarageConfig.vehicleCamera or Config.vehicleCamera)
end
local camCfg = vehicleCamera.camera
SetEntityCoords(cache.ped, camCfg.ped.x, camCfg.ped.y, camCfg.ped.z, false, false, false, false)
SetEntityHeading(cache.ped, camCfg.ped.w)
lib.requestAnimDict("amb@world_human_leaning@male@wall@back@foot_up@idle_a")
TaskPlayAnim(cache.ped, "amb@world_human_leaning@male@wall@back@foot_up@idle_a", "idle_a", 1.0, 1.0, -1, 1, 0, false, false, false)
TaskLookAtCoord(cache.ped, camCfg.coords.x, camCfg.coords.y, camCfg.coords.z, 5000, 0, 2)
menuCamera = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA",
camCfg.coords.x, camCfg.coords.y, camCfg.coords.z,
camCfg.rotation.x, camCfg.rotation.y, camCfg.rotation.z,
65.0, true, 0
)
RenderScriptCams(true, false, 1, true, true)
FreezePlayerAtGarage(TempVehicle)
EquipFists()
TriggerEvent("advancedgarages:client:PlayOnOne")
RemoveAnimDict("amb@world_human_leaning@male@wall@back@foot_up@idle_a")
local playerBalance = lib.callback.await("advancedgarages:getBalance", false) or 0
local takeOutPrice = 0
if isImpound and Config.ImpoundPrice then
takeOutPrice = Config.ImpoundPrice
else
takeOutPrice = (jobGarageConfig and jobGarageConfig.price) or 0
end
local musicTrack = ""
if SoundEnabled and Config.SoundFiles and #Config.SoundFiles > 0 then
musicTrack = Config.SoundFiles[math.random(#Config.SoundFiles)]
end
-- Nous envoyons à la fois l'action moderne et l'action originale pour compatibilité
local payload = {
visible = true,
vehicles = vehicleList or {},
isJobGarage = (garageVehicles ~= nil),
isImpound = isImpound,
takeOutPrice = takeOutPrice,
balance = playerBalance,
music = musicTrack,
garageId = garageId,
identifier = identifier, -- On envoie enfin QUI on est au NUI
config = { enabledButtons = Config.EnabledButtons }, -- Version nested
enabledButtons = Config.EnabledButtons -- Version root
}
SendReactMessage("open", payload) -- Action Quasar standard
SendReactMessage("toggle_garage_menu", payload) -- Fallback
Wait(700)
SetNuiFocus(true, true)
DoScreenFadeIn(400)
end
exports("OpenGarageMenu", OpenGarageMenu)
RegisterNetEvent("advancedgarages:client:payVehiclePrice")
AddEventHandler("advancedgarages:client:payVehiclePrice", function()
vehicleList = lib.callback.await("advancedgarages:getGarageData", false, currentGarage)
SendReactMessage("update_vehicles", vehicleList)
end)
local function RemoveVehicleKeys(vehicle)
if not Config.Vehiclekeys or Config.Vehiclekeys == "none" then return end
local vehicleModel = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle))
local vehiclePlate = GetVehicleNumberPlateText(vehicle)
RemoveVehiclekeys(vehicleModel, vehiclePlate)
Wait(150)
end
function saveJobVehicle(garageData, vehicle)
local jobOrGang = garageData.job or garageData.gang
local plate = MathTrim(GetVehicleNumberPlateText(vehicle))
Debug("Is Vehicle Access Garage", "Job", jobOrGang, "Vehicle", vehicle, "Model", GetEntityModel(vehicle))
local isOwnedByJob = lib.callback.await("advancedgarages:isVehicleOwnedByJob", false, plate, jobOrGang)
if isOwnedByJob then return true end
local vehicleModel = GetEntityModel(vehicle)
for _, model in pairs(garageData.vehicles) do
if model == vehicleModel then return true end
end
return false
end
function StoreVehicle(garageId, isJobGarage, vehicleEntity)
Debug("Store Vehicle", "Garage", garageId, "IsJob", isJobGarage, "Entity", vehicleEntity)
local vehicle = vehicleEntity or cache.vehicle
if not vehicle then return end
local plate = GetPlate(vehicle)
if isJobGarage then
local garageData = Config.JobGarages[garageId]
local isAllowed = saveJobVehicle(garageData, vehicle, GetEntityModel(vehicle))
if not isAllowed then
return Notification(i18n.t("cannot_store_vehicle"), "error")
end
end
local isOwned = isJobGarage
if not isJobGarage then
isOwned = lib.callback.await("advancedgarages:isVehicleOwned", false, plate)
end
if not isOwned then
return Notification(i18n.t("no_vehicle_owner"), "error")
end
local driverSeatFree = IsVehicleSeatFree(vehicle, -1)
local driverPed = GetPedInVehicleSeat(vehicle, -1)
if driverPed ~= cache.ped and not driverSeatFree then
return Notification(i18n.t("not_driver"), "error")
end
if isJobGarage then
return saveJobVehicle(Config.JobGarages[garageId], vehicle)
end
SaveVehicle(garageId, nil, vehicle)
end
function CheckVehicleType(modelName, garageType)
local vehicleClass = GetVehicleClassFromName(modelName)
if garageType == "boat" then
return vehicleClass == 14
elseif garageType == "plane" then
return vehicleClass == 15 or vehicleClass == 16
elseif garageType == "vehicle" then
return vehicleClass ~= 14 and vehicleClass ~= 15 and vehicleClass ~= 16
end
return true
end
local function GetVehicleTypeFromClass(vehicle)
local vehicleClass = GetVehicleClass(vehicle)
if vehicleClass == 15 or vehicleClass == 16 then return "plane"
elseif vehicleClass == 14 then return "boat"
else return "vehicle"
end
end
local storeVehicleText = i18n.t("drawtext.store_vehicle")
local function HandleStoreVehicleInteraction(garageData, isJobGarage)
local pedCoords = GetEntityCoords(cache.ped)
local spawnCoords = vec3(garageData.coords.spawnCoords.x, garageData.coords.spawnCoords.y, garageData.coords.spawnCoords.z)
local dist = #(pedCoords - spawnCoords)
local waitTime = 500
if garageData.isImpound then return waitTime end
local jobOrGang = garageData.job or garageData.gang
if jobOrGang and dist < 10.0 then waitTime = 0 end
if dist > 50.0 then return waitTime end
waitTime = 0
DrawMarkerZone(garageData.coords.spawnCoords.x, garageData.coords.spawnCoords.y, garageData.coords.spawnCoords.z)
if dist > 7.0 then return waitTime end
DrawText3D(
garageData.coords.spawnCoords.x,
garageData.coords.spawnCoords.y,
garageData.coords.spawnCoords.z + 0.3,
storeVehicleText, "store_vehicle", "E"
)
if IsControlJustPressed(0, Keys.E) then
local vehicle = cache.vehicle
local vehicleModel = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle))
if not isJobGarage then
local vehicleType = GetVehicleTypeFromClass(vehicle)
if vehicleType ~= garageData.type then
Debug("You can't store " .. vehicleModel .. " in [" .. garageData.type .. "] garage type")
Notification(i18n.t("cannot_store_vehicle"), "error")
return waitTime
end
end
local targetGarage = ClosestGarage or garageData
StoreVehicle(targetGarage, isJobGarage)
return waitTime
end
return waitTime
end
if not Config.UseTarget or Config.UseTarget == "none" then
local openGarageText = i18n.t("drawtext.open_garage")
local publicGarageText = i18n.t("drawtext.public_garage")
CreateThread(function()
local function DrawGarageInteraction()
local waitTime = 500
if not ClosestGarage then return waitTime end
local garageData = Config.Garages[ClosestGarage]
if not (IsGarageOwner or garageData.available or IsKeyHolder) then return waitTime end
local authorized = CheckGarageAuthorization(garageData.jobs, garageData.gangs)
if not authorized then return waitTime end
if cache.vehicle then
return HandleStoreVehicleInteraction(garageData)
end
local pedCoords = GetEntityCoords(cache.ped)
local menuCoords = vec3(garageData.coords.menuCoords.x, garageData.coords.menuCoords.y, garageData.coords.menuCoords.z)
local dist = #(pedCoords - menuCoords)
if dist > 20.0 then return waitTime end
waitTime = 0
if dist > 4.0 then return waitTime end
if garageData.type ~= "plane" or garageData.isImpound then
DrawText3D(menuCoords.x, menuCoords.y, menuCoords.z + 0.6, openGarageText, "open_garage", "E")
if IsControlJustPressed(0, Keys.E) then
OpenGarageMenu(ClosestGarage, garageData.isImpound, nil, garageData.type == "boat")
return waitTime
end
end
if garageData.type ~= "plane" then
local showPublic = Config.EnablePublicInteriors or garageData.available or garageData.type == "boat" or garageData.isImpound
if showPublic then
DrawText3D(menuCoords.x, menuCoords.y, menuCoords.z + 0.2, publicGarageText, "public_garage", "G")
if IsControlJustPressed(0, Keys.G) then
GotoShellGarage(ClosestGarage, garageData.coords.spawnCoords, garageData.shell)
return waitTime
end
end
end
return waitTime
end
while true do
local waitTime = DrawGarageInteraction()
Wait(waitTime)
end
end)
end
exports("getVehicleMenu", function()
if not ClosestGarage then
return CheckJobMenu()
end
local garageData = Config.Garages[ClosestGarage]
if not (IsGarageOwner or garageData.available or IsKeyHolder) then
return false
end
if cache.vehicle then
return HandleStoreVehicleInteraction(garageData)
end
return "menu", {
garage = ClosestGarage,
available = garageData.available,
isImpound = garageData.isImpound,
type = garageData.type
}
end)
RegisterNetEvent("advancedgarages:StoreVehicle")
AddEventHandler("advancedgarages:StoreVehicle", function()
if not ClosestGarage then return end
StoreVehicle(ClosestGarage, false)
end)
RegisterNetEvent("advancedgarages:StoreJobVehicle")
AddEventHandler("advancedgarages:StoreJobVehicle", function(data)
StoreVehicle(data.garage, true)
end)
RegisterNetEvent("advancedgarages:OpenGarageMenu")
AddEventHandler("advancedgarages:OpenGarageMenu", function()
if not ClosestGarage then return end
local garageData = Config.Garages[ClosestGarage]
OpenGarageMenu(ClosestGarage, garageData.available, nil, garageData.type == "boat")
end)
RegisterNetEvent("advancedgarages:GotoShell")
AddEventHandler("advancedgarages:GotoShell", function()
if not ClosestGarage then return end
local garageData = Config.Garages[ClosestGarage]
GotoShellGarage(ClosestGarage, garageData.coords.spawnCoords, garageData.shell)
end)
if not Config.UseTarget or Config.UseTarget == "none" then
local openGarageText = i18n.t("drawtext.open_garage")
CreateThread(function()
local function DrawJobGarageInteraction()
local waitTime = 500
for garageIndex, garageData in ipairs(Config.JobGarages) do
local jobOrGang = garageData.job or garageData.gang
if not CheckJob(jobOrGang, garageData.grade) then goto continue end
if cache.vehicle then
HandleStoreVehicleInteraction(garageData, true)
goto continue
end
local pedCoords = GetEntityCoords(cache.ped)
local menuCoords = vec3(garageData.coords.menuCoords.x, garageData.coords.menuCoords.y, garageData.coords.menuCoords.z)
local dist = #(pedCoords - menuCoords)
if dist > 20.0 then goto continue end
waitTime = 0
if dist > 3.0 then goto continue end
DrawText3D(menuCoords.x, menuCoords.y, menuCoords.z + 0.3, openGarageText, "open_garage", "E")
if IsControlJustPressed(0, Keys.E) then
local isAvailable = lib.callback.await("advancedgarages:isGarageAvailable", false, garageIndex)
if not isAvailable then
Notification(i18n.t("garage_not_available"), "error")
goto continue
end
local jobVehicles = lib.callback.await("advancedgarages:getJobVehicles", false, garageData.name, jobOrGang)
for _, v in pairs(jobVehicles) do
v.vehicle = json.encode(v.vehicle)
end
if not garageData.vehicle then
for _, vehicleModel in ipairs(garageData.vehicles) do
local plate = tostring(jobOrGang .. "_" .. math.random(111, 999))
table.insert(jobVehicles, {
id = #jobVehicles + 1,
vehicle = json.encode({ model = vehicleModel, plate = plate }),
plate = plate
})
end
end
TriggerServerEvent("advancedgarages:setInJobGarage", garageIndex, true)
OpenGarageMenu(garageIndex, garageData.isImpound, jobVehicles)
end
::continue::
end
return waitTime
end
while true do
local waitTime = DrawJobGarageInteraction()
Wait(waitTime)
end
end)
end
if not Config.UseTarget or Config.UseTarget == "none" or Config.UseTarget == "qb-radialmenu" then
CreateThread(function()
local recoveryText = ""
local function DrawRecoveryInteraction()
local waitTime = 500
local pedCoords = GetEntityCoords(cache.ped)
for _, recoveryCoords in pairs(Config.Recovery.coords) do
local dist = #(pedCoords - vec3(recoveryCoords.x, recoveryCoords.y, recoveryCoords.z))
if dist <= 20.0 then
waitTime = 0
if dist <= 3.0 then
recoveryText = i18n.t("drawtext.recovery", { price = Config.Recovery.price })
DrawText3D(recoveryCoords.x, recoveryCoords.y, recoveryCoords.z + 0.3, recoveryText, "recovery", "E")
if IsControlJustPressed(0, Keys.E) then
local vehicles = lib.callback.await("advancedgarages:getRecoveryVehicles", false)
if #vehicles == 0 then
return Notification(i18n.t("keyholders.empty_out"), "info")
end
OpenRecoveryMenu(vehicles)
return waitTime
end
end
end
end
return waitTime
end
while true do
local waitTime = DrawRecoveryInteraction()
Wait(waitTime)
end
end)
end
RegisterNetEvent("advancedgarages:OpenJobMenu")
AddEventHandler("advancedgarages:OpenJobMenu", function(data)
garage = data.garage
OpenGarageMenu(garage.garage, garage.isImpound, garage.vehicleList)
end)
function CheckJobMenu()
local pedCoords = GetEntityCoords(cache.ped)
local result = nil
for garageIndex, garageData in pairs(Config.JobGarages) do
local jobOrGang = garageData.job or garageData.gang
if not CheckJob(jobOrGang, garageData.grade) then goto continue end
local menuCoords = vec3(garageData.coords.menuCoords.x, garageData.coords.menuCoords.y, garageData.coords.menuCoords.z)
local dist = #(pedCoords - menuCoords)
if dist > 15.0 then goto continue end
if cache.vehicle then
return HandleStoreVehicleInteraction(garageData, true)
end
local vehicleEntries = {}
for vehicleIdx, vehicleModel in ipairs(garageData.vehicles) do
local plate = tostring(jobOrGang .. "_" .. garageIndex .. vehicleIdx)
table.insert(vehicleEntries, {
vehicle = json.encode({ model = vehicleModel, plate = plate }),
plate = plate
})
end
result = {
garage = garageIndex,
vehicleList = vehicleEntries,
available = garageData.available,
isImpound = garageData.isImpound,
type = garageData.type
}
::continue::
end
if not result then
return false
end
return "job-menu", result
end
CreateThread(function()
while true do
if InGarageMenu or currentlyShellData then
HideHudAndRadarThisFrame()
end
Wait(0)
end
end)
exports("usingGarages", function()
return InGarageMenu or currentlyShellData
end)
function CloseGarageMenu()
-- On force la fermeture peu importe l'état
InGarageMenu = false
currentlyInGarage = false
ToggleHud(true)
TriggerEvent("advancedgarages:GetWeatherSync", false)
TriggerServerEvent("advancedgarages:setInJobGarage", currentGarage, false)
if garageEventListener then
Citizen.RemoveEventListener(garageEventListener)
garageEventListener = nil
end
DoScreenFadeOut(300)
SetNuiFocus(false, false)
ClearPedTasks(cache.ped)
-- On force le déblocage du personnage
FreezeEntityPosition(cache.ped, false)
SetEntityAlpha(cache.ped, 255, false)
dofLoopActive = false
if menuCamera then
Utils.DestroyFlyCam(menuCamera)
menuCamera = nil
end
-- On détruit toutes les caméras résiduelles
RenderScriptCams(false, false, 0, true, true)
DestroyAllCams(true)
Wait(700)
if preGarageCoords then
SetEntityCoords(cache.ped, preGarageCoords.x, preGarageCoords.y, preGarageCoords.z, false, false, false, false)
end
TriggerServerEvent("advancedgarages:RoutePlayerDefault")
TriggerEvent("advancedgarages:client:Stop")
openedManagementMenu = false
Wait(500)
DoScreenFadeIn(500)
end
exports("closeMenu", CloseGarageMenu)
RegisterNUICallback("close_menu", function(data, cb)
CloseGarageMenu()
cb(1)
end)
RegisterNUICallback("toggle_inspect", function(active, cb)
cb(1)
IsCameraAvailable = not active
if active then
if menuCamera then
DestroyCam(menuCamera, false)
menuCamera = nil
end
menuCamera = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA",
vehicleCamera.camera.coords.x, vehicleCamera.camera.coords.y, vehicleCamera.camera.coords.z,
vehicleCamera.camera.rotation.x, vehicleCamera.camera.rotation.y, vehicleCamera.camera.rotation.z,
65.0, false, 0
)
SetCamActive(menuCamera, true)
end
end)
RegisterNUICallback("takeVehicleFromOut", function(data, cb)
local success = lib.callback.await("advancedgarages:takeVehicleFromOut", false, currentGarage, data.plate)
vehicleList = lib.callback.await("advancedgarages:getGarageData", false, currentGarage)
SendReactMessage("update_vehicles", vehicleList)
cb(success)
end)
RegisterNUICallback("transferVehicle", function(data, cb)
print("[GARAGES] NUI Callback: transferVehicle", json.encode(data))
local result = lib.callback.await("advancedgarages:transferVehicle", false, data.garage, data.plate)
cb(result)
end)
RegisterNUICallback("transferVehicleToPlayer", function(data, cb)
print("[GARAGES] NUI Callback: transferVehicleToPlayer", json.encode(data))
local result = lib.callback.await("advancedgarages:transferVehicleToPlayer", false, data.playerId, data.plate)
cb(result)
end)
RegisterNUICallback("setVehicleTag", function(data, cb)
print("[GARAGES] NUI Callback: setVehicleTag", data.tag, data.plate)
TriggerServerEvent("advancedgarages:SetVehicleTag", data.tag, data.plate)
cb(1)
end)
RegisterNUICallback("setVehicleFav", function(data, cb)
print("[GARAGES] NUI Callback: setVehicleFav", data.fav, data.plate)
TriggerServerEvent("advancedgarages:setVehicleFav", data.fav, data.plate)
cb(1)
end)
function DriveVehicle(vehicleProps, spawnCoords)
local hasTimeout = lib.callback.await("advancedgarages:DriveVehicle", false, vehicleProps, spawnCoords, vehicleCamera)
if not hasTimeout then
Notification(i18n.t("vehicle_not_spawned"), "error")
return false
end
while not cache.vehicle do
Debug("Waiting for in vehicle...")
Wait(0)
end
local vehicle = cache.vehicle
Wait(150)
SetVehicleProperties(vehicle, vehicleProps)
Wait(200)
if DoesEntityExist(vehicle) then
local currentPlate = MathTrim(GetVehicleNumberPlateText(vehicle))
local expectedPlate = MathTrim(vehicleProps.plate)
if currentPlate ~= expectedPlate then
Debug("Property verification failed, retrying. Expected:", expectedPlate, "Got:", currentPlate)
SetVehicleProperties(vehicle, vehicleProps)
end
end
return true
end
function ChangeVehicleExtra(vehicle, extraId, enable)
if not DoesExtraExist(vehicle, extraId) then return end
if enable then
SetVehicleExtra(vehicle, extraId, false)
if IsVehicleExtraTurnedOn(vehicle, extraId) then
ChangeVehicleExtra(vehicle, extraId, enable)
end
else
SetVehicleExtra(vehicle, extraId, true)
if not IsVehicleExtraTurnedOn(vehicle, extraId) then
ChangeVehicleExtra(vehicle, extraId, enable)
end
end
end
function SetDefaultVehicleExtras(vehicle, extrasMap)
for extraId = 1, 20 do
if DoesExtraExist(vehicle, extraId) then
SetVehicleExtra(vehicle, extraId, 1)
end
end
for extraId, enabled in pairs(extrasMap) do
local shouldEnable = type(enabled) ~= "boolean" or enabled
ChangeVehicleExtra(vehicle, tonumber(extraId), shouldEnable)
end
end
local function FreezeBoatOnSpawn(vehicle)
if GetVehicleClass(vehicle) ~= 14 then return end
Wait(4000)
Debug("The boat: " .. vehicle .. " froze so as not to be dragged by the tide!")
FreezeEntityPosition(vehicle, true)
end
local function DeleteVehiclesNearCoords(coords)
local refCoords = vec3(coords.x, coords.y, coords.z)
for _, vehicle in ipairs(GetGamePool("CVehicle")) do
local dist = #(refCoords - GetEntityCoords(vehicle))
if dist < 10.0 then
RemoveVehicle(vehicle)
Debug("Removed vehicle " .. vehicle .. " from " .. dist .. " meters")
end
end
end
RegisterNUICallback("spawnVehicle", function(data, cb)
DeleteVehiclesNearCoords(vehicleCamera.vehicleCoords)
local vehicleProps = json.decode(data.props)
local jobGarageData = Config.JobGarages[currentGarage]
local spawned = LocalSpawnVehicle(vehicleProps, vehicleCamera.vehicleCoords, previewVehicle)
if jobGarageData then
local modelName = vehicleProps.model
local livery = jobGarageData.liveries and jobGarageData.liveries[modelName]
SetVehicleLivery(spawned, livery)
local tuning = jobGarageData.tuning and jobGarageData.tuning[modelName] or {}
if tuning then
SetVehicleProperties(spawned, tuning, true)
end
end
SetEntityNoCollisionEntity(cache.ped, spawned, false)
TempVehicle = spawned
previewVehicle = spawned
InGarageMenu = true
local stats = GetVehicleStats(spawned)
FreezeBoatOnSpawn(spawned)
Wait(100)
cb(stats)
end)
RegisterNUICallback("toggle_cinematic_cam", function(active, cb)
cb(1)
Debug("toggle_cinematic_cam", active)
if not IsCameraAvailable then return end
if active then
IsCameraMoving = true
StartIdleCam(TempVehicle)
else
if menuCamera then
DestroyCam(menuCamera, false)
menuCamera = nil
end
menuCamera = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA",
vehicleCamera.camera.coords.x, vehicleCamera.camera.coords.y, vehicleCamera.camera.coords.z,
vehicleCamera.camera.rotation.x, vehicleCamera.camera.rotation.y, vehicleCamera.camera.rotation.z,
65.0, false, 0
)
if LocalData.CurrentPlayerCamera then
SetCamActiveWithInterp(menuCamera, LocalData.CurrentPlayerCamera, 1000, 100, 50)
else
SetCamActive(menuCamera, true)
end
IsCameraMoving = false
end
end)
RegisterNUICallback("driveVehicle", function(data, cb)
local hasTimeout = lib.callback.await("advancedgarages:checkTimeout", false)
if hasTimeout then
return Notification(i18n.t("timeout_expired"), "error")
end
if Config.PlayerToVehicleAnimation then
local garageData = Config.Garages[currentGarage]
if garageData and garageData.type == "vehicle" then
FreezePlayerAtGarage(TempVehicle)
Wait(3500)
end
end
CloseGarageMenu()
IsCameraMoving = false
inPlayerAnim = false
DoScreenFadeOut(150)
Wait(500)
SetNuiFocus(false, false)
if previewVehicle then
RemoveVehicle(previewVehicle)
previewVehicle = nil
end
local garageConfig = (data.isJobGarage and Config.JobGarages[currentGarage]) or Config.Garages[currentGarage]
local spawnCoords
if garageConfig and garageConfig.coords then
spawnCoords = garageConfig.coords.spawnCoords
else
-- Fallback de secours si jamais tout est nil
spawnCoords = Config.Garages[currentGarage] and Config.Garages[currentGarage].coords.spawnCoords
end
if data.isJobGarage then
local price = garageConfig.price or 0
if price > 0 then
local hasMoney = lib.callback.await("advancedgarages:existMoney", false, price)
if not hasMoney then
DoScreenFadeIn(1000)
return Notification(i18n.t("no_money", { price = price }), "error")
end
lib.callback.await("advancedgarages:removeMoney", false, price)
end
local livery = garageConfig.liveries and garageConfig.liveries[data.vehicle.model]
local jobVehicle = SpawnVehicle(data.vehicle, spawnCoords)
Wait(600)
if Config.Vehiclekeys and Config.Vehiclekeys ~= "none" then
local modelName = GetDisplayNameFromVehicleModel(GetEntityModel(jobVehicle))
local plate = GetVehicleNumberPlateText(jobVehicle)
AddVehiclekeys(modelName, plate, true)
end
DoScreenFadeIn(1000)
TaskWarpPedIntoVehicle(cache.ped, jobVehicle, -1)
Wait(600)
if livery then SetVehicleLivery(jobVehicle, livery) end
local extras = garageConfig.extras and garageConfig.extras[data.vehicle.model]
if extras then SetDefaultVehicleExtras(jobVehicle, extras) end
local tuning = garageConfig.tuning and garageConfig.tuning[data.vehicle.model] or {}
if tuning then SetVehicleProperties(jobVehicle, tuning) end
TriggerServerEvent("advancedgarages:takeoutJobVehicle", data.vehicle.plate)
return
end
SpawnVehicleEvents(data.vehicle, data.vehicle.plate)
DriveVehicle(data.vehicle, spawnCoords)
Wait(600)
local vehicle = cache.vehicle
local vehicleModel = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle))
local vehiclePlate = GetVehicleNumberPlateText(vehicle)
if Config.Vehiclekeys and Config.Vehiclekeys ~= "none" then
AddVehiclekeys(vehicleModel, vehiclePlate, true)
end
if Config.Framework == "qb" then
Debug("Vehicle goes to state 0 (OUT)")
TriggerServerEvent("advancedgarages:server:updateVehicleState", 0, vehiclePlate)
end
DoScreenFadeIn(1000)
cb(1)
end)
local function GetVehicleCorners(vehicle)
local minDim, maxDim = GetModelDimensions(GetEntityModel(vehicle))
local padding = 0.0
local corners = {}
corners[1] = GetOffsetFromEntityInWorldCoords(vehicle, minDim.x - padding, minDim.y - padding, minDim.z - padding)
corners[2] = GetOffsetFromEntityInWorldCoords(vehicle, maxDim.x + padding, minDim.y - padding, minDim.z - padding)
corners[3] = GetOffsetFromEntityInWorldCoords(vehicle, maxDim.x + padding, maxDim.y + padding, minDim.z - padding)
corners[4] = GetOffsetFromEntityInWorldCoords(vehicle, minDim.x - padding, maxDim.y + padding, minDim.z - padding)
corners[5] = GetOffsetFromEntityInWorldCoords(vehicle, minDim.x - padding, minDim.y - padding, maxDim.z + padding)
corners[6] = GetOffsetFromEntityInWorldCoords(vehicle, maxDim.x + padding, minDim.y - padding, maxDim.z + padding)
corners[7] = GetOffsetFromEntityInWorldCoords(vehicle, maxDim.x + padding, maxDim.y + padding, maxDim.z + padding)
corners[8] = GetOffsetFromEntityInWorldCoords(vehicle, minDim.x - padding, maxDim.y + padding, maxDim.z + padding)
return corners
end
local function MidPoint(a, b)
return vector3((a.x + b.x) / 2.0, (a.y + b.y) / 2.0, (a.z + b.z) / 2.0)
end
local function OffsetCoords(origin, heading, distance)
local angle = math.rad(heading - 90)
return vector3(
origin.x + distance * math.cos(angle),
origin.y + distance * math.sin(angle),
origin.z
)
end
function GetFrontOfVehicle(vehicle, heading)
local corners = GetVehicleCorners(vehicle)
local midBottom = MidPoint(corners[6], corners[7])
local midFront = MidPoint(corners[1], corners[4])
local center = MidPoint(midBottom, midFront)
local vehiclePos = GetEntityCoords(vehicle)
local elevated = vector3(vehiclePos.x, vehiclePos.y, center.z + 0.5)
local offset = -(#(elevated - midFront)) + (-2.25)
return OffsetCoords(elevated, heading, offset)
end
local currentVehicleRotation = nil
RegisterNUICallback("RotateCam", function(data, cb)
if not DoesEntityExist(TempVehicle) then
return cb("ok")
end
if not currentVehicleRotation then
currentVehicleRotation = GetEntityRotation(TempVehicle)
end
if not data.x then
return cb("ok")
end
local newZ = currentVehicleRotation.z - data.x
currentVehicleRotation = vec3(currentVehicleRotation.x, currentVehicleRotation.y, newZ)
local camPos = GetFrontOfVehicle(TempVehicle, newZ)
coords = camPos
SetCamCoord(menuCamera, camPos.x, camPos.y, camPos.z)
SetCamRot(menuCamera, 0, 0, newZ - 180, 2)
cb("ok")
end)
local function GetNearbyJobGarageData()
local pedCoords = GetEntityCoords(cache.ped)
for _, garageData in ipairs(Config.JobGarages) do
local menuCoords = vec3(garageData.coords.menuCoords.x, garageData.coords.menuCoords.y, garageData.coords.menuCoords.z)
if #(pedCoords - menuCoords) < 15.0 then
return garageData
end
end
return false
end
RegisterCommand("deleteJobVehicle", function(source, args)
local playerJob = GetJobName()
local playerGrade = GetJobGrade()
local nearbyGarage = GetNearbyJobGarageData()
if not nearbyGarage then
return Notification(i18n.t("garage_not_found"), "error")
end
local requiredJob = nearbyGarage.job or nearbyGarage.gang
if not requiredJob then
return Notification(i18n.t("garage_not_found_job"), "error")
end
if requiredJob ~= playerJob then
return Notification(i18n.t("not_correct_job"), "error")
end
if playerGrade < nearbyGarage.grade then
return Notification(i18n.t("not_correct_grade"), "error")
end
OpenDeleteJobVehicleMenu(nearbyGarage.name, requiredJob)
end, false)
RegisterCommand("addJobVehicle", function(source, args)
local playerJob = GetJobName()
local playerGrade = GetJobGrade()
local nearbyGarage = GetNearbyJobGarageData()
if not nearbyGarage then
return Notification(i18n.t("garage_not_found"), "error")
end
local requiredJob = nearbyGarage.job or nearbyGarage.gang
if not requiredJob then
return Notification(i18n.t("garage_not_found_job"), "error")
end
if requiredJob ~= playerJob then
return Notification(i18n.t("not_correct_job"), "error")
end
if nearbyGarage.grade and playerGrade < nearbyGarage.grade then
return Notification(i18n.t("not_correct_grade"), "error")
end
local vehicle = cache.vehicle
if not vehicle then
return Notification(i18n.t("not_in_vehicle"), "error")
end
local plate = GetPlate(vehicle)
local isOwned = lib.callback.await("advancedgarages:isVehicleOwned", false, plate)
if not isOwned then
return Notification(i18n.t("no_vehicle_owner"), "error")
end
TriggerServerEvent("advancedgarages:addJobVehicle", requiredJob, plate, nearbyGarage)
Notification(i18n.t("job_vehicle_added"), "success")
RemoveVehicle(vehicle)
end, false)