1185 lines
40 KiB
Lua
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)
|