diff --git a/[core]/mercyv-garage/GetFramework.lua b/[core]/mercyv-garage/GetFramework.lua new file mode 100644 index 00000000..7a84cdf3 --- /dev/null +++ b/[core]/mercyv-garage/GetFramework.lua @@ -0,0 +1,24 @@ +-- GetFramework.lua +-- Shared between client and server + +function GetFrameworkObject() + local object = nil + if Config.Framework == "esx" then + if Config.NewESX then + object = exports['es_extended']:getSharedObject() + else + while object == nil do + TriggerEvent('esx:getSharedObject', function(obj) object = obj end) + Citizen.Wait(0) + end + end + elseif Config.Framework == "newqb" then + object = exports["qb-core"]:GetCoreObject() + elseif Config.Framework == "oldqb" then + while object == nil do + TriggerEvent('QBCore:GetObject', function(obj) object = obj end) + Citizen.Wait(200) + end + end + return object +end diff --git a/[core]/mercyv-garage/client/admin.lua b/[core]/mercyv-garage/client/admin.lua new file mode 100644 index 00000000..c51d7981 --- /dev/null +++ b/[core]/mercyv-garage/client/admin.lua @@ -0,0 +1,155 @@ +-- ============================================================ +-- mercyv-garage | client/admin.lua +-- Ingame Garage-Editor für Admins +-- ============================================================ + +local AdminOpen = false + +-- Server fragt ob Spieler Admin ist und gibt Ergebnis zurück +local PlayerIsAdmin = false +RegisterNetEvent('mercyv-garage:setAdminStatus', function(status) + PlayerIsAdmin = status + if status then + SendNUIMessage({ action = "SET_ADMIN", isAdmin = true }) + end +end) + +-- Bei Laden einmalig Status anfragen +AddEventHandler('onClientResourceStart', function(res) + if res ~= GetCurrentResourceName() then return end + Citizen.Wait(4000) + TriggerServerEvent('mercyv-garage:checkAdminStatus') +end) + +local function IsLocalAdmin() + return PlayerIsAdmin or IsAceAllowed(Config.AdminAce) +end + +-- ────────────────────────────────────────────────────────────── +-- Admin-Panel öffnen +-- ────────────────────────────────────────────────────────────── + +RegisterCommand('garageadmin', function() + if not IsLocalAdmin() then + Config.ClientNotification(Config.Notify.ADMIN_NO_PERM, "error") + return + end + + AdminOpen = true + GarageIsOpen = true -- Verhindert NPC-Interaction-Loop + + SetNuiFocus(true, true) + exports['hex_4_hud']:HideHud(true) + + SendNUIMessage({ + action = "OPEN_ADMIN", + }) + + -- Garagen-Liste direkt an NUI senden (Callback in main.lua handled das) +end, false) + +-- ────────────────────────────────────────────────────────────── +-- Admin-Panel schließen (NUI Callback) +-- ────────────────────────────────────────────────────────────── + +RegisterNUICallback('closeAdmin', function(data, cb) + AdminOpen = false + GarageIsOpen = false + + SetNuiFocus(false, false) + exports['hex_4_hud']:HideHud(false) + + if PreviewVeh and DoesEntityExist(PreviewVeh) then + DeleteEntity(PreviewVeh) + PreviewVeh = nil + end + + CurrentGarage = nil + -- Kurz warten dann Fokus nochmal freigeben (verhindert stuck-Focus) + Citizen.CreateThread(function() + Citizen.Wait(100) + SetNuiFocus(false, false) + end) + cb({}) +end) + +-- ────────────────────────────────────────────────────────────── +-- Positions-Erfassung: NUI wird temporär deaktiviert +-- Spieler geht zur Position, drückt G oder ESC +-- ────────────────────────────────────────────────────────────── + +local CaptureActive = false +local CaptureField = nil + +RegisterNUICallback('startCapture', function(data, cb) + CaptureField = data.field + + -- NUI Fokus temporär aufheben + SetNuiFocus(false, false) + CaptureActive = true + cb({}) + + -- Capture-Thread starten + Citizen.CreateThread(function() + -- Warte auf G (drücken zum erfassen) oder ESC (abbrechen) + while CaptureActive do + -- On-Screen Anleitung + SetTextFont(4) + SetTextScale(0.5, 0.5) + SetTextColour(255, 255, 255, 255) + SetTextEntry("STRING") + AddTextComponentString("~INPUT_CONTEXT~ Position erfassen ~INPUT_FRONTEND_CANCEL~ Abbrechen") + DrawText(0.35, 0.03) + + if IsControlJustPressed(0, 38) then -- E / G + local ped = PlayerPedId() + local coords = GetEntityCoords(ped) + local heading = GetEntityHeading(ped) + + CaptureActive = false + SetNuiFocus(true, true) + + SendNUIMessage({ + action = "POSITION_CAPTURED", + field = CaptureField, + x = coords.x, + y = coords.y, + z = coords.z, + heading = heading, + }) + elseif IsControlJustPressed(0, 200) then -- ESC + CaptureActive = false + SetNuiFocus(true, true) + SendNUIMessage({ action = "CAPTURE_CANCELLED", field = CaptureField }) + end + + Citizen.Wait(0) + end + end) +end) + +-- ────────────────────────────────────────────────────────────── +-- Admin: Teleport zu Garage +-- ────────────────────────────────────────────────────────────── + +RegisterNUICallback('teleportToGarage', function(data, cb) + if not IsLocalAdmin() then cb({}); return end + if data.x and data.y and data.z then + local ped = PlayerPedId() + SetEntityCoords(ped, data.x, data.y, data.z + 0.5, false, false, false, false) + if data.heading then SetEntityHeading(ped, data.heading) end + end + cb({}) +end) + +-- ────────────────────────────────────────────────────────────── +-- Hilfs-Befehl: Aktuelle Koordinaten ausgeben +-- ────────────────────────────────────────────────────────────── + +RegisterCommand('gcoords', function() + local ped = PlayerPedId() + local c = GetEntityCoords(ped) + local h = GetEntityHeading(ped) + print(string.format("[mercyv-garage] Coords: x=%.4f y=%.4f z=%.4f heading=%.4f", c.x, c.y, c.z, h)) + Config.ClientNotification(string.format("X: %.2f Y: %.2f Z: %.2f H: %.2f", c.x, c.y, c.z, h), "info", 6000) +end, false) diff --git a/[core]/mercyv-garage/client/main.lua b/[core]/mercyv-garage/client/main.lua new file mode 100644 index 00000000..57f6b1af --- /dev/null +++ b/[core]/mercyv-garage/client/main.lua @@ -0,0 +1,1156 @@ +local function normPlate(p) + return string.lower(string.gsub(p or '', '%s+', '')) +end + +-- ============================================================ +-- mercyv-garage | client/main.lua +-- Enthält integriertes Vehicle-Persist System +-- ============================================================ + +local Framework = nil +local Garages = {} +local NpcEntities = {} +local BlipEntities = {} +local GarageIsOpen = false +local CurrentGarage = nil +local PreviewVeh = nil +local GarageCam = nil + +-- Persist + +-- Persist: Cache für eigene Fahrzeuge die draußen sind +local MyOutsidePlates = {} +local PersistVehicles = {} -- [normPlate] = {entity=veh, rawPlate="XYZ 123"} +local ActiveJobVehicles = {} -- [normPlate] = rawPlate, ausgeparkte Job-Fahrzeuge + +-- MyOutsidePlates: [normPlate] = rawPlate (Original aus DB) +local function UpdateMyOutsidePlates(vehicles) + MyOutsidePlates = {} + for _, v in ipairs(vehicles) do + if v.stored == 0 then + MyOutsidePlates[normPlate(v.plate)] = v.plate -- raw plate speichern + end + end +end + +local function AddToMyPlates(plate) + MyOutsidePlates[normPlate(plate)] = plate +end + +local function RemoveFromMyPlates(plate) + MyOutsidePlates[normPlate(plate)] = nil +end + +local OutsidePlates = {} -- [normPlate] = true +local PropsQueue = {} -- [netId] = true (Props bereits angefordert) + +-- ────────────────────────────────────────────────────────────── +-- Framework +-- ────────────────────────────────────────────────────────────── + +Citizen.CreateThread(function() + Framework = GetFrameworkObject() +end) + +-- normPlate weiter oben definiert + +-- ────────────────────────────────────────────────────────────── +-- Garagen empfangen & NPCs/Blips aktualisieren +-- ────────────────────────────────────────────────────────────── + +RegisterNetEvent('mercyv-garage:syncGarages', function(data) + Garages = data or {} + local cnt = 0 + for _ in pairs(Garages) do cnt = cnt + 1 end + print(string.format('[mercyv-garage] syncGarages empfangen: %d Garagen', cnt)) + SpawnAllNpcs() + UpdateAllBlips() +end) + +-- ────────────────────────────────────────────────────────────── +-- Robuste Initialisierung: wartet bis Spieler wirklich ingame ist +-- ────────────────────────────────────────────────────────────── + +local clientReadySent = false +local persistReceived = false -- verhindert doppeltes Spawn + +local function SendClientReady() + if clientReadySent then return end + clientReadySent = true + print('[mercyv-garage] Sende clientReady an Server...') + TriggerServerEvent('mercyv-garage:clientReady') +end + +-- Methode 1: via onClientResourceStart +Citizen.CreateThread(function() + while not NetworkIsPlayerActive(PlayerId()) do Citizen.Wait(500) end + Citizen.Wait(2000) + SendClientReady() + + -- Polling: solange Garagen leer sind, alle 5s nochmal anfragen + Citizen.CreateThread(function() + local attempts = 0 + while true do + Citizen.Wait(5000) + local garageCount = 0 + for _ in pairs(Garages) do garageCount = garageCount + 1 end + if garageCount == 0 and attempts < 8 then + attempts = attempts + 1 + print('[mercyv-garage] Garagen noch leer, erneuter Versuch ' .. attempts) + clientReadySent = false -- reset damit nochmal gesendet werden kann + SendClientReady() + else + break + end + end + end) +end) + +-- Methode 2: ESX playerSpawned (Fallback, nur wenn Garagen noch leer) +AddEventHandler('playerSpawned', function() + Citizen.CreateThread(function() + Citizen.Wait(3000) + local garageCount = 0 + for _ in pairs(Garages) do garageCount = garageCount + 1 end + if garageCount == 0 then + print('[mercyv-garage] playerSpawned Fallback: sende clientReady') + clientReadySent = false + SendClientReady() + end + -- Kein zweites clientReady wenn Garagen schon da sind + end) +end) + +-- ────────────────────────────────────────────────────────────── +-- NPC Verwaltung +-- ────────────────────────────────────────────────────────────── + +function SpawnAllNpcs() + for id, ped in pairs(NpcEntities) do + if DoesEntityExist(ped) then DeleteEntity(ped) end + end + NpcEntities = {} + + for id, g in pairs(Garages) do + if g.npc and g.npc.npc then + local n = g.npc.npc + SpawnNpc(id, n.x, n.y, n.z, n.w, g.npc.npcModel or "a_m_m_prolhost_01") + end + end +end + +function SpawnNpc(id, x, y, z, heading, model) + local hash = GetHashKey(model) + RequestModel(hash) + local t = GetGameTimer() + 5000 + while not HasModelLoaded(hash) do + Citizen.Wait(100) + if GetGameTimer() > t then return end + end + -- Z - 1.0 damit NPC auf dem Boden steht (nicht schwebt) + local ped = CreatePed(4, hash, x, y, z - 1.0, heading or 0.0, false, true) + -- Kurz warten dann auf Boden setzen + Citizen.Wait(200) + PlaceObjectOnGroundProperly(ped) + SetEntityInvincible(ped, true) + SetBlockingOfNonTemporaryEvents(ped, true) + FreezeEntityPosition(ped, true) + SetPedFleeAttributes(ped, 0, false) + SetModelAsNoLongerNeeded(hash) + NpcEntities[id] = ped +end + +-- ────────────────────────────────────────────────────────────── +-- Blip Verwaltung +-- ────────────────────────────────────────────────────────────── + +function UpdateAllBlips() + -- Alte Blips entfernen + for _, blip in pairs(BlipEntities) do + if DoesBlipExist(blip) then RemoveBlip(blip) end + end + BlipEntities = {} + + if not Config.Blip then + print('[mercyv-garage] Blips deaktiviert (Config.Blip = false)') + return + end + + -- Spieler-Job für Job-Garagen + local playerJob = nil + if Framework and Config.Framework == 'esx' then + local pd = Framework.GetPlayerData() + playerJob = pd and pd.job and pd.job.name or nil + end + + local cnt = 0 + for id, g in pairs(Garages) do + if not (g.npc and g.npc.npc) then goto skipBlip end + local n = g.npc.npc + + -- Job-Garage: nur für passenden Job + if g.garage == 'jobgarage' then + if not playerJob or g.access == 'none' or playerJob ~= g.access then goto skipBlip end + local blip = AddBlipForCoord(n.x, n.y, n.z) + SetBlipSprite(blip, 357) + SetBlipColour(blip, (g.blip and g.blip.blipColour) or 3) + SetBlipScale(blip, 0.6) + SetBlipAsShortRange(blip, true) + SetBlipDisplay(blip, 6) -- Karte + Minimap, KEIN Legendeneintrag + BeginTextCommandSetBlipName("STRING") + AddTextComponentSubstringPlayerName(g.label or "Garage") + EndTextCommandSetBlipName(blip) + BlipEntities[id] = blip + cnt = cnt + 1 + goto skipBlip + end + + -- Normale Garage: immer anzeigen (außer Impound hat eigenes Icon) + if true then + local blip = AddBlipForCoord(n.x, n.y, n.z) + SetBlipSprite(blip, 357) + SetBlipColour(blip, (g.blip and g.blip.blipColour) or 3) + SetBlipScale(blip, 0.6) + SetBlipAsShortRange(blip, true) + SetBlipDisplay(blip, 6) -- Karte + Minimap, KEIN Legendeneintrag + BeginTextCommandSetBlipName("STRING") + AddTextComponentSubstringPlayerName(g.label or "Garage") + EndTextCommandSetBlipName(blip) + BlipEntities[id] = blip + cnt = cnt + 1 + end + ::skipBlip:: + end + print(string.format('[mercyv-garage] %d Blips gesetzt.', cnt)) +end + +-- ────────────────────────────────────────────────────────────── +-- E-Interaktion NPC → Garage öffnen +-- ────────────────────────────────────────────────────────────── + +Citizen.CreateThread(function() + local hintShown = false + local nearGarage = nil + + while true do + -- Abstandsprüfung: gedrosselt (alle 300ms) + local ped = PlayerPedId() + local pos = GetEntityCoords(ped) + nearGarage = nil + + for id, g in pairs(Garages) do + if g.npc and g.npc.npc then + local n = g.npc.npc + local dist = #(pos - vector3(n.x, n.y, n.z)) + if dist < 5.0 then + nearGarage = id + break + end + end + end + + if nearGarage and not GarageIsOpen then + if not hintShown then + hintShown = true + exports['hex_4_hud']:ShowHelpNotify("Garage öffnen", "E") + end + -- Jeden Frame auf E prüfen wenn in der Nähe + Citizen.Wait(0) + if IsControlJustPressed(0, 38) then + OpenGarage(nearGarage) + end + else + if hintShown then + exports['hex_4_hud']:HideHelpNotify() + hintShown = false + end + Citizen.Wait(300) -- Weit weg: selten prüfen + end + end +end) + +-- ────────────────────────────────────────────────────────────── +-- Einpark-Marker + Interaktion +-- ────────────────────────────────────────────────────────────── + +Citizen.CreateThread(function() + local hintShown = false + local nearPark = nil + while true do + local sleep = 1500 + local ped = PlayerPedId() + local pos = GetEntityCoords(ped) + nearPark = nil + + if not GarageIsOpen and IsPedInAnyVehicle(ped, false) then + for id, g in pairs(Garages) do + local t = g.garage + if t ~= "impound" and t ~= "impoundboat" and t ~= "impoundplane" then + if g.car and g.car.garage then + local gp = g.car.garage + local dist = #(pos - vector3(gp.x, gp.y, gp.z)) + if dist < Config.ParkRadius * 2 then + sleep = 50 + if dist < Config.ParkRadius then + nearPark = id + end + end + end + end + end + end + + if nearPark and not GarageIsOpen then + if not hintShown then + hintShown = true + exports['hex_4_hud']:ShowHelpNotify("Fahrzeug einparken", "E") + end + if IsControlJustPressed(0, 38) then + ParkVehicle(nearPark) + end + else + if hintShown then + exports['hex_4_hud']:HideHelpNotify() + hintShown = false + end + end + + Citizen.Wait(sleep) + end +end) + +-- ────────────────────────────────────────────────────────────── +-- Garage öffnen / schließen +-- ────────────────────────────────────────────────────────────── + +function OpenGarage(garageId) + if GarageIsOpen then return end + local g = Garages[garageId] + if not g then return end + + GarageIsOpen = true + CurrentGarage = garageId + + exports['hex_4_hud']:HideHud(true) + SetNuiFocus(true, true) + TriggerServerEvent('mercyv-garage:getVehicles', garageId) +end + +function CloseGarage() + GarageIsOpen = false + CurrentGarage = nil + + SetNuiFocus(false, false) + exports['hex_4_hud']:HideHud(false) + + if PreviewVeh and DoesEntityExist(PreviewVeh) then + DeleteEntity(PreviewVeh) + PreviewVeh = nil + end + + SendNUIMessage({ action = "CLOSE" }) +end + +-- ────────────────────────────────────────────────────────────── +-- Fahrzeuge vom Server → NUI senden +-- ────────────────────────────────────────────────────────────── + +RegisterNetEvent('mercyv-garage:receiveVehicles', function(vehicles, garageId) + local g = Garages[garageId] + if not g then return end + + -- Outside-Plates aus Fahrzeug-Liste cachen + UpdateMyOutsidePlates(vehicles) + + local nuiVehicles = {} + for _, v in ipairs(vehicles) do + local modelname = v.modelname or '' + local modelHash = tonumber(modelname) + local displayName + if modelHash then + displayName = GetDisplayNameFromVehicleModel(modelHash) + else + displayName = GetDisplayNameFromVehicleModel(modelname) + end + if not displayName or displayName == "CARNOTFOUND" or displayName == '' then + displayName = v.isJobVehicle and (v.label or v.modelname or 'Dienstfahrzeug') or (modelname ~= '' and modelname or 'Fahrzeug') + end + table.insert(nuiVehicles, { + plate = v.plate, + props = v.props, + modelname = v.modelname, + stored = v.stored, + parking = v.parking, + favorite = v.favorite, + vehClass = v.vehClass or 0, + carname = displayName, + carimage = displayName, + }) + end + + local playerName = '' + if Framework and Config.Framework == 'esx' then + local pd = Framework.GetPlayerData() + playerName = pd and pd.name or '' + end + + -- Fahrzeuge in 30m Umkreis scannen (für Einparken-Button) + local nearbyPlates = {} + local ped = PlayerPedId() + local pedPos = GetEntityCoords(ped) + for _, veh in ipairs(GetGamePool('CVehicle')) do + if DoesEntityExist(veh) and not IsEntityDead(veh) then + local vehPos = GetEntityCoords(veh) + local dist = #(pedPos - vehPos) + if dist <= 30.0 then + local plate = normPlate(GetVehicleNumberPlateText(veh)) + if plate ~= '' then + nearbyPlates[plate] = true + end + end + end + end + + -- Fahrzeuge markieren die in der Nähe sind + for _, v in ipairs(nuiVehicles) do + v.nearby = nearbyPlates[normPlate(v.plate)] == true + -- Job-Fahrzeuge: immer verfügbar + if v.isJobVehicle then + v.stored = 1 + -- Einparken: prüfen ob irgendein Fahrzeug vom gleichen Modell in der Nähe steht + -- das in ActiveJobVehicles getrackt ist + local nearbyJobVehicle = false + for np, rawPlate in pairs(ActiveJobVehicles) do + if nearbyPlates[np] then + -- Modell des nahen Fahrzeugs prüfen + for _, veh in ipairs(GetGamePool('CVehicle')) do + if DoesEntityExist(veh) and normPlate(GetVehicleNumberPlateText(veh)) == np then + local vehModel = GetEntityModel(veh) + local jobModel = GetHashKey(v.modelname or v.plate) + if vehModel == jobModel then + nearbyJobVehicle = true + v.nearbyJobPlate = rawPlate -- echtes Kennzeichen für Einparken + end + break + end + end + end + end + v.nearby = nearbyJobVehicle + end + end + + SendNUIMessage({ + action = "OPEN", + vehicles = nuiVehicles, + garageId = garageId, + garageType = g.garage, + playerName = playerName, + impound = Config.Impound, + impoundPrice = Config.ImpoundPrice, + }) +end) + +-- ────────────────────────────────────────────────────────────── +-- Fahrzeug einparken +-- ────────────────────────────────────────────────────────────── + +function ParkVehicle(garageId) + local ped = PlayerPedId() + local vehicle = GetVehiclePedIsIn(ped, false) + if not DoesEntityExist(vehicle) then return end + + local plate = string.gsub(GetVehicleNumberPlateText(vehicle), '%s+', '') + local class = GetVehicleClass(vehicle) + local g = Garages[garageId] + if not g then return end + + local allowed = Config.AllowedClasses[g.garage] or {} + if not allowed[class] then + Config.ClientNotification(Config.Notify.WRONG_CLASS, "error") + return + end + + local props + if Config.Framework == 'esx' and Framework then + props = json.encode(Framework.Game.GetVehicleProperties(vehicle)) + else + props = json.encode({ model = GetEntityModel(vehicle), plate = plate }) + end + + RemoveVehicleKeys(plate, GetEntityModel(vehicle), vehicle) + TaskLeaveVehicle(ped, vehicle, 0) + Citizen.Wait(1500) + DeleteEntity(vehicle) + + -- Aus Persist-Liste entfernen + OutsidePlates[normPlate(plate)] = nil + RemoveFromMyPlates(plate) + TriggerServerEvent('mercyv-garage:parkIn', plate, garageId, props, class) +end + +-- ────────────────────────────────────────────────────────────── +-- Fahrzeug spawnen (nach TakeOut) +-- ────────────────────────────────────────────────────────────── + +RegisterNetEvent('mercyv-garage:doSpawn', function(data) + SpawnVehicle(data, true) +end) + +function SpawnVehicle(data, closeGarageAfter) + local g = Garages[data.garageId] + if not g then return end + + local gType = g.garage or 'normal' + local spawn + + -- Bei Impound: Fahrzeug neben dem Spieler spawnen, nicht am Abschlepphof + if gType == 'impound' or gType == 'impoundboat' or gType == 'impoundplane' then + local pedCoords = GetEntityCoords(PlayerPedId()) + local heading = GetEntityHeading(PlayerPedId()) + -- 5m vor dem Spieler + local rad = math.rad(heading) + spawn = { + x = pedCoords.x + math.sin(-rad) * 5.0, + y = pedCoords.y + math.cos(-rad) * 5.0, + z = pedCoords.z, + w = heading, + } + else + spawn = g.car.spawncar + end + local props = type(data.props) == 'string' and json.decode(data.props) or data.props + local model = nil + + -- Job-Fahrzeug: Modell direkt aus modelname (Modellname als String) + if data.isJobVehicle then + model = GetHashKey(data.modelname or 'adder') + else + -- Normal: aus Props (Hash-Integer) + if props and props.model then + model = tonumber(props.model) or GetHashKey(tostring(props.model)) + end + if not model or model == 0 then + local mn = data.modelname or '' + if mn ~= '' then model = tonumber(mn) or GetHashKey(mn) end + end + if not model or model == 0 then model = GetHashKey('adder') end + end + + RequestModel(model) + local t = GetGameTimer() + 8000 + while not HasModelLoaded(model) do + Citizen.Wait(100) + if GetGameTimer() > t then + Config.ClientNotification("Fahrzeug konnte nicht geladen werden.", "error") + return + end + end + + -- Spawn-Platz prüfen + local nearVeh = GetClosestVehicle(spawn.x, spawn.y, spawn.z, 3.0, 0, 71) + if DoesEntityExist(nearVeh) then + Config.ClientNotification(Config.Notify.SPAWN_BLOCKED, "error") + SetModelAsNoLongerNeeded(model) + TriggerServerEvent('mercyv-garage:parkIn', data.plate, data.garageId, + type(data.props) == 'string' and data.props or json.encode(props), 0) + return + end + + local vehicle = CreateVehicle(model, spawn.x, spawn.y, spawn.z, spawn.w or 0.0, true, false) + local tw = GetGameTimer() + 3000 + while not DoesEntityExist(vehicle) do + if GetGameTimer() > tw then break end + Citizen.Wait(100) + end + + SetVehicleNumberPlateText(vehicle, data.plate) + + if props and Config.Framework == 'esx' and Framework then + Framework.Game.SetVehicleProperties(vehicle, props) + end + + Config.SetVehicleFuel(vehicle, props and props.fuelLevel or 90) + SetModelAsNoLongerNeeded(model) + SetVehicleEngineOn(vehicle, true, true, false) + GiveVehicleKeys(data.plate, model, vehicle) + + -- Job-Fahrzeug: allen Spielern mit gleichem Job Schlüssel geben + if data.isJobVehicle and data.jobAccess then + TriggerServerEvent('mercyv-garage:giveJobKeys', data.plate, data.jobAccess) + -- Random-Plate tracken für Einparken-Button + local np = normPlate(data.plate) + ActiveJobVehicles[np] = data.plate + -- Entity direkt tracken damit Einparken-Erkennung funktioniert + PersistVehicles[np] = { entity = vehicle, rawPlate = data.plate } + end + + -- In Persist-Liste aufnehmen + OutsidePlates[normPlate(data.plate)] = true + AddToMyPlates(data.plate) + + -- Sofort Position speichern (raw plate für DB-Match) + local rawPlateForSave = data.plate -- original aus DB + Citizen.CreateThread(function() + Citizen.Wait(800) + for _, veh in ipairs(GetGamePool('CVehicle')) do + if DoesEntityExist(veh) then + if normPlate(GetVehicleNumberPlateText(veh)) == normPlate(rawPlateForSave) then + local coords = GetEntityCoords(veh) + TriggerServerEvent('mercyv-garage:saveCoords', {{ + plate = rawPlateForSave, -- RAW plate + x = coords.x, y = coords.y, z = coords.z, + heading = GetEntityHeading(veh), + }}) + print('[mercyv-garage Persist] Initiale Position gespeichert: ' .. rawPlateForSave) + break + end + end + end + end) + + if closeGarageAfter then CloseGarage() end + Config.ClientNotification(Config.Notify.TOOK_OUT, "success") +end + +-- ────────────────────────────────────────────────────────────── +-- ══════════ INTEGRIERTES PERSIST SYSTEM ══════════ +-- ────────────────────────────────────────────────────────────── + +-- Außen-Kennzeichen empfangen +RegisterNetEvent('mercyv-garage:outsidePlates', function(plates) + OutsidePlates = {} + for _, p in ipairs(plates) do + OutsidePlates[p] = true + end +end) + +-- Fahrzeuge nach Restart spawnen (Server schickt Liste beim Join) +RegisterNetEvent('mercyv-garage:persistSpawn', function(list) + if not list or #list == 0 then return end + if persistReceived then + print('[mercyv-garage Persist] Duplikat-Spawn ignoriert.') + return + end + persistReceived = true + print(string.format('[mercyv-garage Persist] persistSpawn empfangen: %d Fahrzeuge', #list)) + + Citizen.CreateThread(function() + -- Warten bis Spieler wirklich in der Welt ist (Z > -50 = nicht im Ladescreen) + local waitTimeout = GetGameTimer() + 30000 + repeat + Citizen.Wait(500) + until GetEntityCoords(PlayerPedId()).z > -50.0 or GetGameTimer() > waitTimeout + + Citizen.Wait(1000) -- Extra-Puffer nach Spawn + + for _, v in ipairs(list) do + OutsidePlates[v.plate] = true + AddToMyPlates(v.plate) + end + + local spawned = {} + print(string.format('[mercyv-garage Persist] Spawne %d Fahrzeuge nach Restart...', #list)) + + for _, v in ipairs(list) do + repeat + -- Vorhandene Fahrzeuge mit diesem Kennzeichen löschen + PersistVehicles[normPlate(v.plate)] = nil + for _, veh in ipairs(GetGamePool('CVehicle')) do + if DoesEntityExist(veh) and normPlate(GetVehicleNumberPlateText(veh)) == normPlate(v.plate) then + SetEntityAsMissionEntity(veh, true, true) + DeleteEntity(veh) + end + end + Citizen.Wait(200) + + local modelHash = nil + if v.modelname and v.modelname ~= '' then + local asNum = tonumber(v.modelname) + if asNum then + modelHash = asNum -- direkt als Integer verwenden + else + modelHash = GetHashKey(v.modelname) -- Modellname → Hash + end + end + + -- Fallback: aus Props JSON lesen + if (not modelHash or modelHash == 0) and v.props then + local ok, pd = pcall(json.decode, v.props) + if ok and pd and pd.model then + modelHash = tonumber(pd.model) or GetHashKey(tostring(pd.model)) + end + end + + if not modelHash or modelHash == 0 then break end + + RequestModel(modelHash) + local tModel = GetGameTimer() + 5000 + while not HasModelLoaded(modelHash) do + if GetGameTimer() > tModel then + SetModelAsNoLongerNeeded(modelHash) + break + end + Citizen.Wait(100) + end + if not HasModelLoaded(modelHash) then break end + + local veh = CreateVehicle(modelHash, v.x, v.y, v.z, v.heading, true, false) + local tVeh = GetGameTimer() + 3000 + while not DoesEntityExist(veh) do + if GetGameTimer() > tVeh then break end + Citizen.Wait(100) + end + + if DoesEntityExist(veh) then + SetVehicleNumberPlateText(veh, v.plate) + SetEntityAsMissionEntity(veh, true, true) + + if v.props then + local ok, pd = pcall(json.decode, v.props) + if ok and pd then + if Config.Framework == 'esx' and Framework then + Framework.Game.SetVehicleProperties(veh, pd) + end + if pd.fuelLevel then Config.SetVehicleFuel(veh, pd.fuelLevel) end + end + end + + SetModelAsNoLongerNeeded(modelHash) + table.insert(spawned, v.plate) + print('[mercyv-garage Persist] Gespawnt: ' .. v.plate) + end + until true -- repeat...until true = einmaliger Block mit break-Unterstützung + + Citizen.Wait(150) + end + + if #spawned > 0 then + TriggerServerEvent('mercyv-garage:persistMarkSpawned', spawned) + end + end) +end) + +-- ────────────────────────────────────────────────────────────── +-- Koordinaten speichern (simpel: alle Fahrzeuge des Spielers im Pool) +-- ────────────────────────────────────────────────────────────── + +Citizen.CreateThread(function() + Citizen.Wait(5000) + local lastSaved = {} + + while true do + Citizen.Wait(Config.PersistSaveInterval or 1000) + + -- Alle Fahrzeuge im Pool prüfen + local toSave = {} + local checkedPlates = {} + + -- Methode 1: Direkt getrackte Persist-Entities (auch wenn weit weg) + for normP, data in pairs(PersistVehicles) do + if DoesEntityExist(data.entity) and not IsEntityDead(data.entity) then + local coords = GetEntityCoords(data.entity) + local last = lastSaved[normP] + local moved = not last or + math.abs(coords.x - last.x) > 0.5 or + math.abs(coords.y - last.y) > 0.5 or + math.abs(coords.z - last.z) > 0.5 + if moved then + table.insert(toSave, { + plate = data.rawPlate, + x = coords.x, y = coords.y, z = coords.z, + heading = GetEntityHeading(data.entity), + }) + lastSaved[normP] = { x = coords.x, y = coords.y, z = coords.z } + end + checkedPlates[normP] = true + else + -- Entity existiert nicht mehr → aus Tracking entfernen + PersistVehicles[normP] = nil + end + end + + -- Methode 2: GetGamePool für normal ausgeparkte Fahrzeuge + for _, veh in ipairs(GetGamePool('CVehicle')) do + if DoesEntityExist(veh) and not IsEntityDead(veh) then + local plate = normPlate(GetVehicleNumberPlateText(veh)) + if not checkedPlates[plate] then + local rawPlate = MyOutsidePlates[plate] + if rawPlate then + local coords = GetEntityCoords(veh) + local last = lastSaved[plate] + local moved = not last or + math.abs(coords.x - last.x) > 0.5 or + math.abs(coords.y - last.y) > 0.5 or + math.abs(coords.z - last.z) > 0.5 + if moved then + table.insert(toSave, { + plate = rawPlate, + x = coords.x, + y = coords.y, + z = coords.z, + heading = GetEntityHeading(veh), + }) + lastSaved[plate] = { x = coords.x, y = coords.y, z = coords.z } + end + end + end -- if not checkedPlates + end + end + + if #toSave > 0 then + TriggerServerEvent('mercyv-garage:saveCoords', toSave) + end + end +end) + +-- ────────────────────────────────────────────────────────────── +-- NUI Callbacks +-- ────────────────────────────────────────────────────────────── + +RegisterNUICallback('close', function(data, cb) + CloseGarage() + cb({}) +end) + +-- Admin aus der Garage heraus öffnen +RegisterNUICallback('openAdminFromGarage', function(data, cb) + if PreviewVeh and DoesEntityExist(PreviewVeh) then + DeleteEntity(PreviewVeh) + PreviewVeh = nil + end + -- NUI bleibt offen, Admin-Panel anzeigen + SendNUIMessage({ action = "OPEN_ADMIN" }) + cb({}) +end) + +RegisterNUICallback('takeOut', function(data, cb) + if not CurrentGarage then cb({}); return end + TriggerServerEvent('mercyv-garage:takeOut', data.plate, CurrentGarage, data.vehClass or 0) + cb({}) +end) + +RegisterNUICallback('previewVehicle', function(data, cb) + if not CurrentGarage then cb({}); return end + local g = Garages[CurrentGarage] + if not g or not g.car or not g.car.showcar then cb({}); return end + local sc = g.car.showcar + local model = GetHashKey(data.modelname or '') + if PreviewVeh and DoesEntityExist(PreviewVeh) then DeleteEntity(PreviewVeh); PreviewVeh = nil end + if model == 0 then cb({}); return end + RequestModel(model) + Citizen.CreateThread(function() + local t = GetGameTimer() + 5000 + while not HasModelLoaded(model) do + if GetGameTimer() > t then return end + Citizen.Wait(50) + end + PreviewVeh = CreateVehicle(model, sc.x, sc.y, sc.z, sc.w or 0.0, false, false) + SetEntityInvincible(PreviewVeh, true) + FreezeEntityPosition(PreviewVeh, true) + if data.props then + local ok, pd = pcall(json.decode, data.props) + if ok and pd and Config.Framework == 'esx' and Framework then + Framework.Game.SetVehicleProperties(PreviewVeh, pd) + end + end + SetModelAsNoLongerNeeded(model) + end) + cb({}) +end) + +RegisterNUICallback('setFavorite', function(data, cb) + TriggerServerEvent('mercyv-garage:setFavorite', data.plate, data.value) + cb({}) +end) + +RegisterNUICallback('adminSaveGarage', function(data, cb) + TriggerServerEvent('mercyv-garage:admin:saveGarage', data) + cb({}) +end) + +RegisterNUICallback('adminDeleteGarage', function(data, cb) + TriggerServerEvent('mercyv-garage:admin:deleteGarage', data.id) + cb({}) +end) + +RegisterNUICallback('capturePosition', function(data, cb) + local ped = PlayerPedId() + local c = GetEntityCoords(ped) + cb({ x = c.x, y = c.y, z = c.z, w = GetEntityHeading(ped) }) +end) + +-- Admin-Garagen empfangen und ans NUI schicken +RegisterNetEvent('mercyv-garage:admin:receiveGarages', function(list) + SendNUIMessage({ action = "ADMIN_GARAGES", garages = list }) +end) + +RegisterNUICallback('getAdminGarages', function(data, cb) + -- Sofort Client-Cache zurückgeben falls vorhanden + local list = {} + for id, g in pairs(Garages) do + table.insert(list, { + id = id, label = g.label or id, type = g.garage, access = g.access or "none", + npc_model = g.npc and g.npc.npcModel or "a_m_m_prolhost_01", + npc_x = g.npc and g.npc.npc and g.npc.npc.x or 0, + npc_y = g.npc and g.npc.npc and g.npc.npc.y or 0, + npc_z = g.npc and g.npc.npc and g.npc.npc.z or 0, + npc_heading = g.npc and g.npc.npc and g.npc.npc.w or 0, + spawn_x = g.car and g.car.spawncar and g.car.spawncar.x or 0, + spawn_y = g.car and g.car.spawncar and g.car.spawncar.y or 0, + spawn_z = g.car and g.car.spawncar and g.car.spawncar.z or 0, + spawn_heading = g.car and g.car.spawncar and g.car.spawncar.w or 0, + park_x = g.car and g.car.garage and g.car.garage.x or 0, + park_y = g.car and g.car.garage and g.car.garage.y or 0, + park_z = g.car and g.car.garage and g.car.garage.z or 0, + showcar_x = g.car and g.car.showcar and g.car.showcar.x, + showcar_y = g.car and g.car.showcar and g.car.showcar.y, + showcar_z = g.car and g.car.showcar and g.car.showcar.z, + showcar_heading = g.car and g.car.showcar and g.car.showcar.w or 0, + cam_x = g.camera and g.camera.x, cam_y = g.camera and g.camera.y, + cam_z = g.camera and g.camera.z, cam_rot_z = g.camera and g.camera.rotationZ or -20, + blip_show = g.blip and g.blip.show and 1 or 0, + blip_type = g.blip and g.blip.blipType or 357, + blip_colour = g.blip and g.blip.blipColour or 3, + }) + end + + if #list > 0 then + -- Cache vorhanden → sofort zurückgeben + cb(list) + else + -- Cache leer → Server fragen, Antwort kommt via ADMIN_GARAGES message + TriggerServerEvent('mercyv-garage:admin:requestGarages') + cb({}) + end +end) + +RegisterNUICallback('teleportToGarage', function(data, cb) + if data.x then + SetEntityCoords(PlayerPedId(), data.x, data.y, data.z + 0.5, false, false, false, false) + if data.heading then SetEntityHeading(PlayerPedId(), data.heading) end + end + cb({}) +end) + +-- ────────────────────────────────────────────────────────────── +-- WICHTIGSTE PERSIST-LOGIK: Position beim Verlassen des Fahrzeugs speichern +-- Zuverlässiger als Timer — läuft immer wenn Spieler aussteigt +-- ────────────────────────────────────────────────────────────── + +local lastVehicle = nil + +Citizen.CreateThread(function() + while true do + Citizen.Wait(0) + local ped = PlayerPedId() + local veh = GetVehiclePedIsIn(ped, false) + + if veh ~= 0 then + -- Spieler ist in Fahrzeug + lastVehicle = veh + elseif lastVehicle and lastVehicle ~= 0 then + -- Spieler gerade ausgestiegen + if DoesEntityExist(lastVehicle) then + local plate = GetVehicleNumberPlateText(lastVehicle) + local rawPlate = MyOutsidePlates[normPlate(plate)] + or (PersistVehicles[normPlate(plate)] and PersistVehicles[normPlate(plate)].rawPlate) + + if rawPlate then + local coords = GetEntityCoords(lastVehicle) + local heading = GetEntityHeading(lastVehicle) + TriggerServerEvent('mercyv-garage:saveCoords', {{ + plate = rawPlate, + x = coords.x, + y = coords.y, + z = coords.z, + heading = heading, + }}) + end + end + lastVehicle = nil + end + end +end) + +-- OutsidePlates werden jetzt direkt aus den Fahrzeugdaten gecacht + +-- ────────────────────────────────────────────────────────────── +-- Einparken über Panel-Button (Fahrzeug in 30m Umkreis) +-- ────────────────────────────────────────────────────────────── + +-- Job-Fahrzeug einparken +RegisterNUICallback('parkJobVehicle', function(data, cb) + if not data.plate then cb({}); return end + + -- nearbyJobPlate enthält das echte (random) Kennzeichen + local realPlate = data.nearbyJobPlate or data.plate + local targetPlate = normPlate(realPlate) + local ped = PlayerPedId() + local pedPos = GetEntityCoords(ped) + local foundVeh = nil + + for _, veh in ipairs(GetGamePool('CVehicle')) do + if DoesEntityExist(veh) then + if normPlate(GetVehicleNumberPlateText(veh)) == targetPlate then + local dist = #(pedPos - GetEntityCoords(veh)) + if dist <= 35.0 then + foundVeh = veh + break + end + end + end + end + + if not foundVeh then + Config.ClientNotification("Fahrzeug nicht in der Nähe.", "error") + cb({}); return + end + + local vehModel = GetEntityModel(foundVeh) + TaskLeaveVehicle(ped, foundVeh, 0) + Citizen.Wait(1000) + RemoveVehicleKeys(data.plate, vehModel, foundVeh) + SetEntityAsMissionEntity(foundVeh, true, true) + DeleteEntity(foundVeh) + ActiveJobVehicles[targetPlate] = nil + + TriggerServerEvent('mercyv-garage:parkJobVehicle', data.plate) + CloseGarage() + cb({}) +end) + +RegisterNUICallback('parkFromPanel', function(data, cb) + if not CurrentGarage or not data.plate then cb({}); return end + + local targetPlate = normPlate(data.plate) + local ped = PlayerPedId() + local pedPos = GetEntityCoords(ped) + local foundVeh = nil + + -- Fahrzeug mit diesem Kennzeichen in der Nähe suchen + for _, veh in ipairs(GetGamePool('CVehicle')) do + if DoesEntityExist(veh) then + local plate = normPlate(GetVehicleNumberPlateText(veh)) + if plate == targetPlate then + local dist = #(pedPos - GetEntityCoords(veh)) + if dist <= 35.0 then + foundVeh = veh + break + end + end + end + end + + if not foundVeh then + Config.ClientNotification("Fahrzeug nicht in der Nähe gefunden.", "error") + cb({}) + return + end + + local g = Garages[CurrentGarage] + local class = GetVehicleClass(foundVeh) + local allowed = Config.AllowedClasses[g and g.garage or "normal"] or {} + if not allowed[class] then + Config.ClientNotification(Config.Notify.WRONG_CLASS, "error") + cb({}) + return + end + + local props + if Config.Framework == 'esx' and Framework then + props = json.encode(Framework.Game.GetVehicleProperties(foundVeh)) + else + props = json.encode({ model = GetEntityModel(foundVeh), plate = data.plate }) + end + + RemoveVehicleKeys(data.plate, GetEntityModel(foundVeh), foundVeh) + TaskLeaveVehicle(ped, foundVeh, 0) + Citizen.Wait(1500) + DeleteEntity(foundVeh) + + OutsidePlates[targetPlate] = nil + RemoveFromMyPlates(targetPlate) + TriggerServerEvent('mercyv-garage:parkIn', data.plate, CurrentGarage, props, class) + + CloseGarage() + cb({}) +end) + +-- ────────────────────────────────────────────────────────────── +-- Fahrzeug-Zerstörungs-Detektor +-- ────────────────────────────────────────────────────────────── + +Citizen.CreateThread(function() + while true do + Citizen.Wait(2000) + + -- Alle getracken Persist-Fahrzeuge auf Zerstörung prüfen + for np, data in pairs(PersistVehicles) do + if not DoesEntityExist(data.entity) or IsEntityDead(data.entity) then + -- Fahrzeug zerstört oder verschwunden + print('[mercyv-garage] Fahrzeug zerstört: ' .. tostring(data.rawPlate)) + TriggerServerEvent('mercyv-garage:vehicleDestroyed', data.rawPlate) + PersistVehicles[np] = nil + RemoveFromMyPlates(data.rawPlate) + end + end + + -- Auch normal ausgeparkte Fahrzeuge überwachen (via MyOutsidePlates) + for np, rawPlate in pairs(MyOutsidePlates) do + -- Suche das Fahrzeug im Pool + local found = false + for _, veh in ipairs(GetGamePool('CVehicle')) do + if DoesEntityExist(veh) and not IsEntityDead(veh) then + if normPlate(GetVehicleNumberPlateText(veh)) == np then + found = true + break + end + end + end + -- Wenn nicht gefunden und nicht in PersistVehicles getrackt → wahrscheinlich zerstört + if not found and not PersistVehicles[np] then + -- Nur melden wenn Spieler in der Nähe der letzten bekannten Position war + -- (verhindert false positives durch Streaming) + -- Wir prüfen ob das Fahrzeug in GetGamePool je sichtbar war + -- Konservativ: nur wenn Fahrzeug aus PersistVehicles stammte + end + end + end +end) + +-- ────────────────────────────────────────────────────────────── +-- /dv Command: Fahrzeug löschen → kommt in Impound +-- ────────────────────────────────────────────────────────────── + +RegisterCommand('dv', function() + local ped = PlayerPedId() + local veh = GetVehiclePedIsIn(ped, false) + + if not DoesEntityExist(veh) then + -- Nächstes Fahrzeug in 5m suchen + veh = GetClosestVehicle(GetEntityCoords(ped), 5.0, 0, 71) + end + + if not DoesEntityExist(veh) then + Config.ClientNotification("Kein Fahrzeug in der Nähe.", "error") + return + end + + local plate = GetVehicleNumberPlateText(veh) + + -- Fahrzeug löschen und ins Impound senden + TaskLeaveVehicle(ped, veh, 0) + Citizen.Wait(500) + DeleteEntity(veh) + + -- Aus Tracking entfernen + local np = normPlate(plate) + PersistVehicles[np] = nil + RemoveFromMyPlates(plate) + + -- Server: Fahrzeug → Impound + TriggerServerEvent('mercyv-garage:sendToImpound', plate) + + Config.ClientNotification("Fahrzeug wurde abgeschleppt.", "info") +end, false) diff --git a/[core]/mercyv-garage/config.lua b/[core]/mercyv-garage/config.lua new file mode 100644 index 00000000..e510c4a1 --- /dev/null +++ b/[core]/mercyv-garage/config.lua @@ -0,0 +1,165 @@ +Config = {} + +-- ══════════════════════════════════════════════════════════════ +-- FRAMEWORK & DATABASE +-- ══════════════════════════════════════════════════════════════ + +Config.Framework = "esx" -- "esx" | "newqb" | "oldqb" +Config.NewESX = true +Config.SQL = "oxmysql" + +-- ══════════════════════════════════════════════════════════════ +-- FEATURES +-- ══════════════════════════════════════════════════════════════ + +Config.Blip = true -- Map-Blips anzeigen +Config.Debug = false -- Debug-Ausgaben in der Console + +-- Fahrzeug-Schlüssel System +Config.Vehiclekey = true +Config.VehicleKeySystem = "jaksam" -- jaksam | qs-vehiclekeys | wasabi-carlock | qb-vehiclekeys +Config.VehicleRemoveKey = true +Config.VehicleRemoveKeySystem = "jaksam" + +-- Kraftstoff System +Config.EnableFuel = true +Config.FuelSystem = "x-fuel" -- LegacyFuel | x-fuel | ox-fuel | nd-fuel | frfuel | cdn-fuel + +-- Impound +Config.Impound = true +Config.ImpoundPrice = 1500 +Config.MoneyType = "bank" + +-- Marker Farbe (Einpark-Zone) +Config.MarkerR = 255 +Config.MarkerG = 165 +Config.MarkerB = 0 +Config.MarkerA = 200 + +-- NPC Einstellungen (standard) +Config.DefaultNpcModel = "a_m_m_prolhost_01" +Config.NpcInteractRadius = 2.5 -- Abstand zum NPC für E-Interaktion +Config.ParkRadius = 6.0 -- Einpark-Radius + +-- Wie oft (ms) Fahrzeugpositionen gespeichert werden (Standard: 30 Sek.) +Config.PersistSaveInterval = 1000 + +-- ══════════════════════════════════════════════════════════════ +-- ADMIN +-- ══════════════════════════════════════════════════════════════ + +-- ACE Permission die admins brauchen: add_ace group.admin mercyv-garage.admin allow +Config.AdminAce = "mercyv-garage.admin" + +-- ══════════════════════════════════════════════════════════════ +-- BENACHRICHTIGUNGEN +-- ══════════════════════════════════════════════════════════════ + +Config.Notify = { + GARAGE_OPEN_LATE = "Garage wird geladen...", + NO_VEHICLE = "Du besitzt kein Fahrzeug.", + NOT_OWNED = "Dieses Fahrzeug gehört dir nicht.", + WRONG_CLASS = "Diese Fahrzeugklasse ist hier nicht erlaubt.", + SPAWN_BLOCKED = "Spawn-Platz ist belegt.", + PARKED_IN = "Fahrzeug eingeparkt.", + TOOK_OUT = "Fahrzeug ausgeparkt.", + NO_ACCESS = "Kein Zugang zu dieser Garage.", + NO_MONEY = "Nicht genug Geld.", + IMPOUND_PAID = "Gebühr bezahlt.", + ALREADY_OUT = "Dieses Fahrzeug ist bereits draußen.", + ADMIN_SAVED = "Garage gespeichert.", + ADMIN_DELETED = "Garage gelöscht.", + ADMIN_NO_PERM = "Keine Berechtigung.", +} + +-- ══════════════════════════════════════════════════════════════ +-- NOTIFICATION FUNCTIONS (anpassen wenn nötig) +-- ══════════════════════════════════════════════════════════════ + +Config.ClientNotification = function(message, msgType, length) + exports['hex_4_hud']:Notify("Garage", message, msgType or "info", length or 3000) +end + +Config.ServerNotification = function(source, message, msgType, length) + TriggerClientEvent("hex_4_hud:Notify", source, "Garage", message, msgType or "info", length or 3000) +end + +-- ══════════════════════════════════════════════════════════════ +-- FAHRZEUG-KLASSEN +-- ══════════════════════════════════════════════════════════════ + +Config.AllowedClasses = { + ["normal"] = {[0]=true,[1]=true,[2]=true,[3]=true,[4]=true,[5]=true, + [6]=true,[7]=true,[8]=true,[9]=true,[10]=true,[11]=true, + [12]=true,[13]=true,[17]=true,[18]=true,[19]=true,[20]=true}, + ["aircraft"] = {[15]=true,[16]=true}, + ["boat"] = {[14]=true}, + ["jobgarage"]= {[0]=true,[1]=true,[2]=true,[3]=true,[4]=true,[5]=true, + [6]=true,[7]=true,[8]=true,[9]=true,[10]=true,[11]=true, + [12]=true,[13]=true,[14]=true,[15]=true,[16]=true, + [17]=true,[18]=true,[19]=true,[20]=true}, + ["impound"] = {[0]=true,[1]=true,[2]=true,[3]=true,[4]=true,[5]=true, + [6]=true,[7]=true,[8]=true,[9]=true,[10]=true,[11]=true, + [12]=true,[13]=true,[14]=true,[15]=true,[16]=true, + [17]=true,[18]=true,[19]=true,[20]=true}, +} + +-- ══════════════════════════════════════════════════════════════ +-- JOB-FAHRZEUGE (für Jobgaragen) +-- Diese Fahrzeuge werden in der Job-Garage angezeigt +-- ══════════════════════════════════════════════════════════════ + +Config.JobVehicles = { + ["police"] = { + { model = "gbpolbisonhf", label = "Polizei Bison HF", grade = 0 }, + { model = "police3", label = "Polizei Cruiser", grade = 0 }, + { model = "police4", label = "Polizei Buffalo", grade = 0 }, + { model = "police2", label = "Polizei Stanier", grade = 0 }, + { model = "gbpolbisonstx", label = "Polizei Bison STX", grade = 0 }, + }, + ["ambulance"] = { + { model = "ambulance", label = "Krankenwagen", grade = 0 }, + { model = "frogger", label = "Frogger", grade = 0 }, + }, +} + +-- ══════════════════════════════════════════════════════════════ +-- FUEL HELPER +-- ══════════════════════════════════════════════════════════════ + +Config.SetVehicleFuel = function(vehicle, level) + if not Config.EnableFuel then + SetVehicleFuelLevel(vehicle, (level or 90) + 0.0) + return + end + local fs = Config.FuelSystem + if fs == 'LegacyFuel' then exports["LegacyFuel"]:SetFuel(vehicle, level) + elseif fs == 'x-fuel' then exports["x-fuel"]:SetFuel(vehicle, level) + elseif fs == 'ox-fuel' then SetVehicleFuelLevel(vehicle, level + 0.0) + elseif fs == 'nd-fuel' then exports["nd-fuel"]:SetFuel(vehicle, level) + elseif fs == 'frfuel' then exports.frfuel:setFuel(vehicle, level) + elseif fs == 'cdn-fuel' then exports['cdn-fuel']:SetFuel(vehicle, level) + else SetVehicleFuelLevel(vehicle, (level or 90) + 0.0) end +end + +-- ══════════════════════════════════════════════════════════════ +-- KEY HELPERS +-- ══════════════════════════════════════════════════════════════ + +function GiveVehicleKeys(plate, model, vehicle) + if not Config.Vehiclekey then return end + local ks = Config.VehicleKeySystem + if ks == 'jaksam' then TriggerServerEvent("vehicles_keys:selfGiveVehicleKeys", plate) + elseif ks == 'qs-vehiclekeys' then exports['qs-vehiclekeys']:GiveKeys(plate, GetDisplayNameFromVehicleModel(model)) + elseif ks == 'wasabi-carlock' then exports.wasabi_carlock:GiveKey(plate) + elseif ks == 'qb-vehiclekeys' then TriggerServerEvent('qb-vehiclekeys:server:AcquireVehicleKeys', plate) end +end + +function RemoveVehicleKeys(plate, model, vehicle) + if not Config.VehicleRemoveKey then return end + local ks = Config.VehicleRemoveKeySystem + if ks == 'jaksam' then TriggerServerEvent("vehicles_keys:selfRemoveKeys", plate) + elseif ks == 'qs-vehiclekeys' then exports['qs-vehiclekeys']:RemoveKeysAuto() + elseif ks == 'wasabi-carlock' then exports.wasabi_carlock:RemoveKey(plate) + elseif ks == 'qb-vehiclekeys' then TriggerServerEvent('qb-vehiclekeys:client:RemoveKeys', plate) end +end diff --git a/[core]/mercyv-garage/fxmanifest.lua b/[core]/mercyv-garage/fxmanifest.lua new file mode 100644 index 00000000..3af4977a --- /dev/null +++ b/[core]/mercyv-garage/fxmanifest.lua @@ -0,0 +1,34 @@ +fx_version 'cerulean' +game 'gta5' +lua54 'yes' +author 'MercyV' +description 'mercyv-garage - Custom Garage System' +version '1.0' + +shared_scripts { + 'config.lua', +} + +server_scripts { + '@oxmysql/lib/MySQL.lua', + 'server/main.lua', +} + +client_scripts { + 'GetFramework.lua', + 'client/main.lua', + 'client/admin.lua', +} + +ui_page 'nui/index.html' + +files { + 'nui/index.html', + 'nui/style.css', + 'nui/script.js', + 'nui/vue.js', + 'nui/fonts/*.*', + 'nui/images/*.*', + 'nui/images/logo/*.*', + 'nui/images/cars/*.*', +} diff --git a/[core]/mercyv-garage/install.sql b/[core]/mercyv-garage/install.sql new file mode 100644 index 00000000..a37fb6f7 --- /dev/null +++ b/[core]/mercyv-garage/install.sql @@ -0,0 +1,200 @@ +-- ============================================================ +-- mercyv-garage | install.sql +-- Einmalig ausführen! +-- ============================================================ + +CREATE TABLE IF NOT EXISTS `mercyv_garages` ( + `id` VARCHAR(80) NOT NULL, + `label` VARCHAR(100) NOT NULL DEFAULT 'Garage', + `type` VARCHAR(20) NOT NULL DEFAULT 'normal', + `access` VARCHAR(50) NOT NULL DEFAULT 'none', + `gang` VARCHAR(50) NOT NULL DEFAULT 'none', + `blip_show` TINYINT(1) NOT NULL DEFAULT 1, + `blip_type` INT NOT NULL DEFAULT 357, + `blip_colour` INT NOT NULL DEFAULT 3, + `npc_model` VARCHAR(100) NOT NULL DEFAULT 'a_m_m_prolhost_01', + `npc_x` FLOAT NOT NULL DEFAULT 0, + `npc_y` FLOAT NOT NULL DEFAULT 0, + `npc_z` FLOAT NOT NULL DEFAULT 0, + `npc_heading` FLOAT NOT NULL DEFAULT 0, + `spawn_x` FLOAT NOT NULL DEFAULT 0, + `spawn_y` FLOAT NOT NULL DEFAULT 0, + `spawn_z` FLOAT NOT NULL DEFAULT 0, + `spawn_heading` FLOAT NOT NULL DEFAULT 0, + `park_x` FLOAT NOT NULL DEFAULT 0, + `park_y` FLOAT NOT NULL DEFAULT 0, + `park_z` FLOAT NOT NULL DEFAULT 0, + `showcar_x` FLOAT DEFAULT NULL, + `showcar_y` FLOAT DEFAULT NULL, + `showcar_z` FLOAT DEFAULT NULL, + `showcar_heading` FLOAT DEFAULT 0, + `cam_x` FLOAT DEFAULT NULL, + `cam_y` FLOAT DEFAULT NULL, + `cam_z` FLOAT DEFAULT NULL, + `cam_rot_z` FLOAT DEFAULT -20, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Klassen-Spalte zu owned_vehicles (ESX) hinzufügen +ALTER TABLE `owned_vehicles` + ADD COLUMN IF NOT EXISTS `veh_class` TINYINT DEFAULT 0; + +-- ============================================================ +-- Standard-Garagen aus der Config (können ingame geändert werden) +-- ============================================================ + +INSERT IGNORE INTO `mercyv_garages` + (id, label, type, access, blip_show, blip_type, blip_colour, npc_model, + npc_x, npc_y, npc_z, npc_heading, + spawn_x, spawn_y, spawn_z, spawn_heading, + park_x, park_y, park_z, + showcar_x, showcar_y, showcar_z, showcar_heading, + cam_x, cam_y, cam_z, cam_rot_z) +VALUES +('Garage A','Garage A','normal','none',1,357,3,'a_m_m_prolhost_01', + 214.5806,-806.8969,30.8052,336.3445, + 233.7616,-802.9507,30.4636,71.0069, + 214.9846,-790.6285,30.8301, + 236.39,-779.89,30.67,161.68, + 234.57,-785.1,30.59,-20.0), + +('Garage B','Garage B','normal','none',1,357,3,'a_m_m_prolhost_01', + 275.95,-344.06,45.17,165.24, + 292.79,-332.22,44.92,161.25, + 271.68,-341.61,44.92, + 274.63,-330.28,44.70,164.27, + 273.08,-335.04,44.92,-20.0), + +('Garage D','Garage D','normal','none',1,357,3,'a_m_m_prolhost_01', + 68.35,13.85,69.21,167.77, + 73.24,11.78,68.85,155.92, + 73.24,11.78,68.85, + 59.35,24.31,69.73,245.08, + 64.78,22.19,69.54,70.0), + +('Garage E','Garage E','normal','none',1,357,3,'a_m_m_prolhost_01', + 363.48,296.86,103.50,244.81, + 367.98,296.57,103.42,345.36, + 367.98,296.57,103.42, + 376.33,288.82,103.20,69.26, + 371.18,290.66,103.31,-110.0), + +('Garage F','Garage F','normal','none',1,357,3,'a_m_m_prolhost_01', + -1158.51,-740.67,19.89,41.16, + -1169.03,-743.49,19.63,42.38, + -1169.03,-743.49,19.63, + -1145.2,-759.03,18.82,39.92, + -1148.57,-754.86,18.97,-140.0), + +('Garage G','Garage G','normal','none',1,357,3,'a_m_m_prolhost_01', + -795.33,-2023.8,9.17,66.37, + -790.11,-2022.68,8.87,58.85, + -791.39,-2030.26,8.87, + -763.11,-2042.28,8.91,37.29, + -766.54,-2037.82,8.9,-143.0), + +('Garage H','Garage H','normal','none',1,357,3,'a_m_m_prolhost_01', + -468.87,-819.67,30.52,358.04, + -472.16,-812.83,30.53,179.63, + -453.49,-814.23,30.58, + -472.02,-800.43,30.54,183.47, + -472.16,-806.15,30.54,-3.0), + +('Garage I','Garage I','normal','none',1,357,3,'a_m_m_prolhost_01', + 1142.38,2661.28,38.16,92.19, + 1137.57,2674.86,38.25,1.08, + 1137.59,2653.02,38.0, + 1121.15,2665.03,38.02,266.97, + 1127.68,2664.84,38.02,88.0), + +('Garage J','Garage J','normal','none',1,357,3,'a_m_m_prolhost_01', + 83.51,6420.3,31.76,313.17, + 85.93,6426.8,31.34,38.93, + 79.68,6417.33,31.28, + 112.65,6396.47,31.31,42.5, + 107.37,6402.14,31.33,-138.0), + +('Garage K','Boot Garage','boat','none',1,356,3,'a_m_m_prolhost_01', + -717.9,-1327.46,1.6,50.86, + -718.05,-1334.24,-0.44,222.71, + -718.03,-1334.21,1.0, + -723.7,-1329.22,-0.11,229.03, + -719.57,-1332.72,1.41,50.0), + +('Garage L','Flugzeug Garage','aircraft','none',1,359,3,'a_m_m_prolhost_01', + -1251.69,-3399.94,13.94,59.19, + -1246.91,-3355.14,13.95,330.68, + -1246.91,-3355.14,13.95, + -1273.01,-3402.28,13.94,331.01, + -1268.42,-3394.32,13.94,-210.0), + +('Garage M','Garage M','normal','none',1,357,3,'a_m_m_prolhost_01', + 271.94,-1509.32,29.18,87.30, + 243.22,-1502.84,29.14,222.92, + 243.22,-1502.84,29.14, + 253.68,-1511.65,29.14,260.14, + 256.95,-1500.42,29.14,-200.0), + +('Garage N','Garage N','normal','none',1,357,3,'a_m_m_prolhost_01', + -1134.78,2682.73,18.46,132.28, + -1155.64,2665.05,18.09,223.0, + -1141.49,2680.13,18.09, + -1157.10,2672.85,18.09,175.18, + -1145.82,2670.54,19.75,-280.0), + +('Garage T','Garage T','normal','none',1,357,3,'a_m_m_prolhost_01', + 302.3,-189.94,61.57,73.01, + 288.09,-194.55,61.57,249.13, + 301.1,-183.12,61.59, + 274.64,-189.47,61.57,252.0, + 279.92,-191.36,61.57,61.57), + +('Impound Garage','Abschlepphof','impound','none',1,68,3,'a_m_m_prolhost_01', + 406.88,-1625.23,29.29,229.89, + 408.0,-1645.66,29.29,228.92, + 408.0,-1645.66,29.29, + 401.4,-1639.93,29.29,230.79, + 406.0,-1643.48,29.29,50.29), + +('Impound Boat','Abschlepphof Boot','impoundboat','none',1,357,3,'a_m_m_prolhost_01', + -769.64,-1425.65,1.60,230.0, + -786.56,-1424.55,-0.51,133.50, + -786.56,-1424.55,-0.51, + -786.56,-1424.55,-0.51,133.50, + -795.95,-1436.94,3.06,322.48), + +('Impound Plane','Abschlepphof Flugzeug','impoundplane','none',1,357,3,'a_m_m_prolhost_01', + -1030.27,-3016.30,13.95,339.01, + -979.81,-2995.32,13.95,69.51, + -979.81,-2995.32,13.95, + -984.89,-3012.54,13.95,61.43, + -994.19,-3008.20,13.95,236.98), + +('police','Polizei Garage','jobgarage','police',0,357,3,'ig_solomon', + 457.60,-977.66,21.95,87.43, + 449.80,-971.60,21.45,177.42, + 449.42,-979.14,21.45, + 417.47,-974.65,21.45,177.18, + 428.37,-974.73,21.45,-270.0), + +('ambulance','Ambulanz Garage','jobgarage','ambulance',0,357,3,'ig_solomon', + -286.81,-588.56,27.78,1.87, + -286.29,-576.06,27.63,85.73, + -285.72,-580.70,27.63, + -305.70,-567.67,27.63,296.93, + -311.61,-563.86,27.63,-110.0); + +-- ============================================================ +-- Vehicle Persist: last_coords Spalte +-- (wird automatisch vom Script genutzt) +-- ============================================================ + +-- ESX: +ALTER TABLE `owned_vehicles` + ADD COLUMN IF NOT EXISTS `veh_class` TINYINT DEFAULT 0, + ADD COLUMN IF NOT EXISTS `last_coords` VARCHAR(255) DEFAULT NULL; + +-- QBCore: +-- ALTER TABLE `player_vehicles` +-- ADD COLUMN IF NOT EXISTS `veh_class` TINYINT DEFAULT 0, +-- ADD COLUMN IF NOT EXISTS `last_coords` VARCHAR(255) DEFAULT NULL; diff --git a/[core]/mercyv-garage/nui/fonts/Gilroy-ExtraBold.otf b/[core]/mercyv-garage/nui/fonts/Gilroy-ExtraBold.otf new file mode 100644 index 00000000..7413e3d4 Binary files /dev/null and b/[core]/mercyv-garage/nui/fonts/Gilroy-ExtraBold.otf differ diff --git a/[core]/mercyv-garage/nui/fonts/Gilroy-Light.otf b/[core]/mercyv-garage/nui/fonts/Gilroy-Light.otf new file mode 100644 index 00000000..dbc05129 Binary files /dev/null and b/[core]/mercyv-garage/nui/fonts/Gilroy-Light.otf differ diff --git a/[core]/mercyv-garage/nui/images/cars/Adder.png b/[core]/mercyv-garage/nui/images/cars/Adder.png new file mode 100644 index 00000000..6bd64c06 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Adder.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Airbus.png b/[core]/mercyv-garage/nui/images/cars/Airbus.png new file mode 100644 index 00000000..b8e3c78d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Airbus.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Akuma.png b/[core]/mercyv-garage/nui/images/cars/Akuma.png new file mode 100644 index 00000000..027cb5b8 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Akuma.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Alpha.png b/[core]/mercyv-garage/nui/images/cars/Alpha.png new file mode 100644 index 00000000..b06f978e Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Alpha.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Alphaz1.png b/[core]/mercyv-garage/nui/images/cars/Alphaz1.png new file mode 100644 index 00000000..12a6fe91 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Alphaz1.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Ambulance.png b/[core]/mercyv-garage/nui/images/cars/Ambulance.png new file mode 100644 index 00000000..b4272db8 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Ambulance.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Ardent.png b/[core]/mercyv-garage/nui/images/cars/Ardent.png new file mode 100644 index 00000000..8a174e38 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Ardent.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Asbo.png b/[core]/mercyv-garage/nui/images/cars/Asbo.png new file mode 100644 index 00000000..27cbee9c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Asbo.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Asea.png b/[core]/mercyv-garage/nui/images/cars/Asea.png new file mode 100644 index 00000000..f85cd9a3 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Asea.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Asea2.png b/[core]/mercyv-garage/nui/images/cars/Asea2.png new file mode 100644 index 00000000..4fbb7bef Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Asea2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Asterope.png b/[core]/mercyv-garage/nui/images/cars/Asterope.png new file mode 100644 index 00000000..32c8598b Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Asterope.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Autarch.png b/[core]/mercyv-garage/nui/images/cars/Autarch.png new file mode 100644 index 00000000..6b83aea3 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Autarch.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Avarus.png b/[core]/mercyv-garage/nui/images/cars/Avarus.png new file mode 100644 index 00000000..44377ce9 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Avarus.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Bagger.png b/[core]/mercyv-garage/nui/images/cars/Bagger.png new file mode 100644 index 00000000..59715bf1 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Bagger.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Baller.png b/[core]/mercyv-garage/nui/images/cars/Baller.png new file mode 100644 index 00000000..a3587ed0 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Baller.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Baller2.png b/[core]/mercyv-garage/nui/images/cars/Baller2.png new file mode 100644 index 00000000..75f902a9 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Baller2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Baller3.png b/[core]/mercyv-garage/nui/images/cars/Baller3.png new file mode 100644 index 00000000..8bee4ed9 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Baller3.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Baller4.png b/[core]/mercyv-garage/nui/images/cars/Baller4.png new file mode 100644 index 00000000..eff9ff89 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Baller4.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Baller5.png b/[core]/mercyv-garage/nui/images/cars/Baller5.png new file mode 100644 index 00000000..04e297b2 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Baller5.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Baller6.png b/[core]/mercyv-garage/nui/images/cars/Baller6.png new file mode 100644 index 00000000..83f6f13a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Baller6.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Banshee.png b/[core]/mercyv-garage/nui/images/cars/Banshee.png new file mode 100644 index 00000000..b34fb0d2 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Banshee.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Banshee2.png b/[core]/mercyv-garage/nui/images/cars/Banshee2.png new file mode 100644 index 00000000..5e775115 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Banshee2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Bati.png b/[core]/mercyv-garage/nui/images/cars/Bati.png new file mode 100644 index 00000000..4967fb88 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Bati.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Bati2.png b/[core]/mercyv-garage/nui/images/cars/Bati2.png new file mode 100644 index 00000000..b38405af Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Bati2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Bestiagts.png b/[core]/mercyv-garage/nui/images/cars/Bestiagts.png new file mode 100644 index 00000000..37066a09 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Bestiagts.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Bf400.png b/[core]/mercyv-garage/nui/images/cars/Bf400.png new file mode 100644 index 00000000..e29457ee Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Bf400.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Bfinject.png b/[core]/mercyv-garage/nui/images/cars/Bfinject.png new file mode 100644 index 00000000..127242d3 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Bfinject.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Bifta.png b/[core]/mercyv-garage/nui/images/cars/Bifta.png new file mode 100644 index 00000000..9280f75c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Bifta.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Bison.png b/[core]/mercyv-garage/nui/images/cars/Bison.png new file mode 100644 index 00000000..c4198d14 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Bison.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Bison2.png b/[core]/mercyv-garage/nui/images/cars/Bison2.png new file mode 100644 index 00000000..ffd41abb Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Bison2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Bison3.png b/[core]/mercyv-garage/nui/images/cars/Bison3.png new file mode 100644 index 00000000..831000e0 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Bison3.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Bjxl.png b/[core]/mercyv-garage/nui/images/cars/Bjxl.png new file mode 100644 index 00000000..92db54db Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Bjxl.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Blade.png b/[core]/mercyv-garage/nui/images/cars/Blade.png new file mode 100644 index 00000000..6d7ac439 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Blade.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Blazer.png b/[core]/mercyv-garage/nui/images/cars/Blazer.png new file mode 100644 index 00000000..00992b17 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Blazer.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Blista.png b/[core]/mercyv-garage/nui/images/cars/Blista.png new file mode 100644 index 00000000..19311d27 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Blista.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Blista2.png b/[core]/mercyv-garage/nui/images/cars/Blista2.png new file mode 100644 index 00000000..d51d6d15 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Blista2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Blista3.png b/[core]/mercyv-garage/nui/images/cars/Blista3.png new file mode 100644 index 00000000..d4d7922a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Blista3.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Bobcatxl.png b/[core]/mercyv-garage/nui/images/cars/Bobcatxl.png new file mode 100644 index 00000000..0e79aa29 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Bobcatxl.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Bodhi2.png b/[core]/mercyv-garage/nui/images/cars/Bodhi2.png new file mode 100644 index 00000000..b626211a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Bodhi2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Brawler.png b/[core]/mercyv-garage/nui/images/cars/Brawler.png new file mode 100644 index 00000000..f76bcf00 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Brawler.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Brioso.png b/[core]/mercyv-garage/nui/images/cars/Brioso.png new file mode 100644 index 00000000..891c710f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Brioso.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Brutus.png b/[core]/mercyv-garage/nui/images/cars/Brutus.png new file mode 100644 index 00000000..c38d88dd Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Brutus.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Brutus2.png b/[core]/mercyv-garage/nui/images/cars/Brutus2.png new file mode 100644 index 00000000..b7612adf Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Brutus2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Btype.png b/[core]/mercyv-garage/nui/images/cars/Btype.png new file mode 100644 index 00000000..7edb4518 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Btype.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Btype2.png b/[core]/mercyv-garage/nui/images/cars/Btype2.png new file mode 100644 index 00000000..bbc89fef Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Btype2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Btype3.png b/[core]/mercyv-garage/nui/images/cars/Btype3.png new file mode 100644 index 00000000..1a226387 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Btype3.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Buccaneer.png b/[core]/mercyv-garage/nui/images/cars/Buccaneer.png new file mode 100644 index 00000000..14e63b5b Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Buccaneer.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Buffalo.png b/[core]/mercyv-garage/nui/images/cars/Buffalo.png new file mode 100644 index 00000000..7c3ad9c3 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Buffalo.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Bullet.png b/[core]/mercyv-garage/nui/images/cars/Bullet.png new file mode 100644 index 00000000..d55eabaf Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Bullet.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Carbonizzare.png b/[core]/mercyv-garage/nui/images/cars/Carbonizzare.png new file mode 100644 index 00000000..4ec901ef Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Carbonizzare.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Carbonrs.png b/[core]/mercyv-garage/nui/images/cars/Carbonrs.png new file mode 100644 index 00000000..abfe56e5 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Carbonrs.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Casco.png b/[core]/mercyv-garage/nui/images/cars/Casco.png new file mode 100644 index 00000000..62257f9f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Casco.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Cavalcade.png b/[core]/mercyv-garage/nui/images/cars/Cavalcade.png new file mode 100644 index 00000000..3cdded26 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Cavalcade.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Cavalcade2.png b/[core]/mercyv-garage/nui/images/cars/Cavalcade2.png new file mode 100644 index 00000000..abd50ae4 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Cavalcade2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Cheburek.png b/[core]/mercyv-garage/nui/images/cars/Cheburek.png new file mode 100644 index 00000000..b4d81699 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Cheburek.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Cheetah.png b/[core]/mercyv-garage/nui/images/cars/Cheetah.png new file mode 100644 index 00000000..5c8f8698 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Cheetah.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Cheetah2.png b/[core]/mercyv-garage/nui/images/cars/Cheetah2.png new file mode 100644 index 00000000..6287ba4d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Cheetah2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Chimera.png b/[core]/mercyv-garage/nui/images/cars/Chimera.png new file mode 100644 index 00000000..b37a12be Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Chimera.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Chino.png b/[core]/mercyv-garage/nui/images/cars/Chino.png new file mode 100644 index 00000000..e8b50beb Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Chino.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Chino2.png b/[core]/mercyv-garage/nui/images/cars/Chino2.png new file mode 100644 index 00000000..c25562cd Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Chino2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Cliffhanger.png b/[core]/mercyv-garage/nui/images/cars/Cliffhanger.png new file mode 100644 index 00000000..1632f114 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Cliffhanger.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Clique.png b/[core]/mercyv-garage/nui/images/cars/Clique.png new file mode 100644 index 00000000..015cb3a4 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Clique.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Club.png b/[core]/mercyv-garage/nui/images/cars/Club.png new file mode 100644 index 00000000..1a14269a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Club.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Cog55.png b/[core]/mercyv-garage/nui/images/cars/Cog55.png new file mode 100644 index 00000000..8cf192a2 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Cog55.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Cog552.png b/[core]/mercyv-garage/nui/images/cars/Cog552.png new file mode 100644 index 00000000..4dc5a9fe Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Cog552.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Cogcabrio.png b/[core]/mercyv-garage/nui/images/cars/Cogcabrio.png new file mode 100644 index 00000000..e9825ef8 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Cogcabrio.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Cognoscenti Cabrio.png b/[core]/mercyv-garage/nui/images/cars/Cognoscenti Cabrio.png new file mode 100644 index 00000000..c102e6a7 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Cognoscenti Cabrio.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Cognoscenti-Cabrio.png b/[core]/mercyv-garage/nui/images/cars/Cognoscenti-Cabrio.png new file mode 100644 index 00000000..99059ed8 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Cognoscenti-Cabrio.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Comet2.png b/[core]/mercyv-garage/nui/images/cars/Comet2.png new file mode 100644 index 00000000..592f837f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Comet2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Comet3.png b/[core]/mercyv-garage/nui/images/cars/Comet3.png new file mode 100644 index 00000000..82b80923 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Comet3.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Comet4.png b/[core]/mercyv-garage/nui/images/cars/Comet4.png new file mode 100644 index 00000000..3400df37 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Comet4.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Comet5.png b/[core]/mercyv-garage/nui/images/cars/Comet5.png new file mode 100644 index 00000000..c7d3d7ab Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Comet5.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Contender.png b/[core]/mercyv-garage/nui/images/cars/Contender.png new file mode 100644 index 00000000..bccdbd70 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Contender.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Coquette.png b/[core]/mercyv-garage/nui/images/cars/Coquette.png new file mode 100644 index 00000000..caac60a2 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Coquette.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Coquette2.png b/[core]/mercyv-garage/nui/images/cars/Coquette2.png new file mode 100644 index 00000000..46eda62c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Coquette2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Coquette3.png b/[core]/mercyv-garage/nui/images/cars/Coquette3.png new file mode 100644 index 00000000..10f5c9da Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Coquette3.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Coquette4.png b/[core]/mercyv-garage/nui/images/cars/Coquette4.png new file mode 100644 index 00000000..87655b58 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Coquette4.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Cyclone.png b/[core]/mercyv-garage/nui/images/cars/Cyclone.png new file mode 100644 index 00000000..efe0fa16 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Cyclone.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Daemon.png b/[core]/mercyv-garage/nui/images/cars/Daemon.png new file mode 100644 index 00000000..db512c15 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Daemon.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Daemon2.png b/[core]/mercyv-garage/nui/images/cars/Daemon2.png new file mode 100644 index 00000000..5dfbc634 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Daemon2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Defiler.png b/[core]/mercyv-garage/nui/images/cars/Defiler.png new file mode 100644 index 00000000..01176f3c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Defiler.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Deluxo.png b/[core]/mercyv-garage/nui/images/cars/Deluxo.png new file mode 100644 index 00000000..4f716234 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Deluxo.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Deveste.png b/[core]/mercyv-garage/nui/images/cars/Deveste.png new file mode 100644 index 00000000..ab71cb26 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Deveste.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Deviant.png b/[core]/mercyv-garage/nui/images/cars/Deviant.png new file mode 100644 index 00000000..5b0ecc5f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Deviant.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Dloader.png b/[core]/mercyv-garage/nui/images/cars/Dloader.png new file mode 100644 index 00000000..0dbd5276 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Dloader.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Double.png b/[core]/mercyv-garage/nui/images/cars/Double.png new file mode 100644 index 00000000..83a79e43 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Double.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Dukes.png b/[core]/mercyv-garage/nui/images/cars/Dukes.png new file mode 100644 index 00000000..ed7504c6 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Dukes.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Dynasty.png b/[core]/mercyv-garage/nui/images/cars/Dynasty.png new file mode 100644 index 00000000..0659a31b Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Dynasty.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Elegy.png b/[core]/mercyv-garage/nui/images/cars/Elegy.png new file mode 100644 index 00000000..41fdcdaa Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Elegy.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Elegy2.png b/[core]/mercyv-garage/nui/images/cars/Elegy2.png new file mode 100644 index 00000000..12964740 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Elegy2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Ellie.png b/[core]/mercyv-garage/nui/images/cars/Ellie.png new file mode 100644 index 00000000..d2788f37 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Ellie.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Emerus.png b/[core]/mercyv-garage/nui/images/cars/Emerus.png new file mode 100644 index 00000000..84b59686 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Emerus.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Emperor.png b/[core]/mercyv-garage/nui/images/cars/Emperor.png new file mode 100644 index 00000000..27232301 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Emperor.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Enduro.png b/[core]/mercyv-garage/nui/images/cars/Enduro.png new file mode 100644 index 00000000..49eef7a0 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Enduro.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Entity2.png b/[core]/mercyv-garage/nui/images/cars/Entity2.png new file mode 100644 index 00000000..39f5c313 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Entity2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Entityxf.png b/[core]/mercyv-garage/nui/images/cars/Entityxf.png new file mode 100644 index 00000000..43b66a0c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Entityxf.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Esskey.png b/[core]/mercyv-garage/nui/images/cars/Esskey.png new file mode 100644 index 00000000..2b774c8a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Esskey.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Everon.png b/[core]/mercyv-garage/nui/images/cars/Everon.png new file mode 100644 index 00000000..b3a65d54 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Everon.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Exemplar.png b/[core]/mercyv-garage/nui/images/cars/Exemplar.png new file mode 100644 index 00000000..816f5ed7 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Exemplar.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/F620.png b/[core]/mercyv-garage/nui/images/cars/F620.png new file mode 100644 index 00000000..6d91279d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/F620.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Faction.png b/[core]/mercyv-garage/nui/images/cars/Faction.png new file mode 100644 index 00000000..a864833d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Faction.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Faction2.png b/[core]/mercyv-garage/nui/images/cars/Faction2.png new file mode 100644 index 00000000..66e17a7b Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Faction2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Fagaloa.png b/[core]/mercyv-garage/nui/images/cars/Fagaloa.png new file mode 100644 index 00000000..1789e2ac Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Fagaloa.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Faggio.png b/[core]/mercyv-garage/nui/images/cars/Faggio.png new file mode 100644 index 00000000..b2481178 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Faggio.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Fcr.png b/[core]/mercyv-garage/nui/images/cars/Fcr.png new file mode 100644 index 00000000..f638e017 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Fcr.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Felon.png b/[core]/mercyv-garage/nui/images/cars/Felon.png new file mode 100644 index 00000000..8b8d8c4e Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Felon.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Felon2.png b/[core]/mercyv-garage/nui/images/cars/Felon2.png new file mode 100644 index 00000000..799ab548 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Felon2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Feltzer2.png b/[core]/mercyv-garage/nui/images/cars/Feltzer2.png new file mode 100644 index 00000000..ea6fa732 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Feltzer2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Feltzer3.png b/[core]/mercyv-garage/nui/images/cars/Feltzer3.png new file mode 100644 index 00000000..f58a8323 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Feltzer3.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Flashgt.png b/[core]/mercyv-garage/nui/images/cars/Flashgt.png new file mode 100644 index 00000000..94094435 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Flashgt.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Fmj.png b/[core]/mercyv-garage/nui/images/cars/Fmj.png new file mode 100644 index 00000000..c9e7c8c9 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Fmj.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Fq2.png b/[core]/mercyv-garage/nui/images/cars/Fq2.png new file mode 100644 index 00000000..df85de20 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Fq2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Freecrawler.png b/[core]/mercyv-garage/nui/images/cars/Freecrawler.png new file mode 100644 index 00000000..4e0ad8e1 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Freecrawler.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Fugitive.png b/[core]/mercyv-garage/nui/images/cars/Fugitive.png new file mode 100644 index 00000000..966f086c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Fugitive.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Furia.png b/[core]/mercyv-garage/nui/images/cars/Furia.png new file mode 100644 index 00000000..8eba3026 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Furia.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Furoregt.png b/[core]/mercyv-garage/nui/images/cars/Furoregt.png new file mode 100644 index 00000000..798ddc59 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Furoregt.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Fusilade.png b/[core]/mercyv-garage/nui/images/cars/Fusilade.png new file mode 100644 index 00000000..1745ac43 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Fusilade.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Futo.png b/[core]/mercyv-garage/nui/images/cars/Futo.png new file mode 100644 index 00000000..f4bf9ec6 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Futo.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Gargoyle.png b/[core]/mercyv-garage/nui/images/cars/Gargoyle.png new file mode 100644 index 00000000..efbe486a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Gargoyle.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Gauntlet.png b/[core]/mercyv-garage/nui/images/cars/Gauntlet.png new file mode 100644 index 00000000..f78801fe Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Gauntlet.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Gauntlet2.png b/[core]/mercyv-garage/nui/images/cars/Gauntlet2.png new file mode 100644 index 00000000..a1159d17 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Gauntlet2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Gb200.png b/[core]/mercyv-garage/nui/images/cars/Gb200.png new file mode 100644 index 00000000..300cd813 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Gb200.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Glendale.png b/[core]/mercyv-garage/nui/images/cars/Glendale.png new file mode 100644 index 00000000..fd0154e2 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Glendale.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Glendale2.png b/[core]/mercyv-garage/nui/images/cars/Glendale2.png new file mode 100644 index 00000000..91ab46e7 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Glendale2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Gp1.png b/[core]/mercyv-garage/nui/images/cars/Gp1.png new file mode 100644 index 00000000..916b7e79 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Gp1.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Granger.png b/[core]/mercyv-garage/nui/images/cars/Granger.png new file mode 100644 index 00000000..0c61cb10 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Granger.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Gresley.png b/[core]/mercyv-garage/nui/images/cars/Gresley.png new file mode 100644 index 00000000..b899a713 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Gresley.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Gt500.png b/[core]/mercyv-garage/nui/images/cars/Gt500.png new file mode 100644 index 00000000..20891bbe Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Gt500.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Habanero.png b/[core]/mercyv-garage/nui/images/cars/Habanero.png new file mode 100644 index 00000000..64ffdbce Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Habanero.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Hakuchou.png b/[core]/mercyv-garage/nui/images/cars/Hakuchou.png new file mode 100644 index 00000000..9049a81f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Hakuchou.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Hellion.png b/[core]/mercyv-garage/nui/images/cars/Hellion.png new file mode 100644 index 00000000..e95224e3 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Hellion.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Hermes.png b/[core]/mercyv-garage/nui/images/cars/Hermes.png new file mode 100644 index 00000000..8183e519 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Hermes.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Hexer.png b/[core]/mercyv-garage/nui/images/cars/Hexer.png new file mode 100644 index 00000000..4f4924c6 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Hexer.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Hotknife.png b/[core]/mercyv-garage/nui/images/cars/Hotknife.png new file mode 100644 index 00000000..61e239eb Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Hotknife.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Hotring.png b/[core]/mercyv-garage/nui/images/cars/Hotring.png new file mode 100644 index 00000000..1b741a1a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Hotring.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Huntley.png b/[core]/mercyv-garage/nui/images/cars/Huntley.png new file mode 100644 index 00000000..7aa85114 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Huntley.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Hustler.png b/[core]/mercyv-garage/nui/images/cars/Hustler.png new file mode 100644 index 00000000..9767eae2 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Hustler.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Imorgon.png b/[core]/mercyv-garage/nui/images/cars/Imorgon.png new file mode 100644 index 00000000..584816cb Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Imorgon.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Impaler.png b/[core]/mercyv-garage/nui/images/cars/Impaler.png new file mode 100644 index 00000000..19af636a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Impaler.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Impaler2.png b/[core]/mercyv-garage/nui/images/cars/Impaler2.png new file mode 100644 index 00000000..13350e57 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Impaler2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Impaler3.png b/[core]/mercyv-garage/nui/images/cars/Impaler3.png new file mode 100644 index 00000000..bfe898b8 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Impaler3.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Impaler4.png b/[core]/mercyv-garage/nui/images/cars/Impaler4.png new file mode 100644 index 00000000..66241944 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Impaler4.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Imperator.png b/[core]/mercyv-garage/nui/images/cars/Imperator.png new file mode 100644 index 00000000..75a7114c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Imperator.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Imperator2.png b/[core]/mercyv-garage/nui/images/cars/Imperator2.png new file mode 100644 index 00000000..9d3dfd80 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Imperator2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Infernus.png b/[core]/mercyv-garage/nui/images/cars/Infernus.png new file mode 100644 index 00000000..93708078 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Infernus.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Infernus2.png b/[core]/mercyv-garage/nui/images/cars/Infernus2.png new file mode 100644 index 00000000..3de042c5 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Infernus2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Ingot.png b/[core]/mercyv-garage/nui/images/cars/Ingot.png new file mode 100644 index 00000000..7163d108 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Ingot.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Innovation.png b/[core]/mercyv-garage/nui/images/cars/Innovation.png new file mode 100644 index 00000000..a8d14fca Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Innovation.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Intruder.png b/[core]/mercyv-garage/nui/images/cars/Intruder.png new file mode 100644 index 00000000..f7d78344 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Intruder.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Issi2.png b/[core]/mercyv-garage/nui/images/cars/Issi2.png new file mode 100644 index 00000000..6f73d434 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Issi2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Issi3.png b/[core]/mercyv-garage/nui/images/cars/Issi3.png new file mode 100644 index 00000000..7804ca7a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Issi3.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Issi4.png b/[core]/mercyv-garage/nui/images/cars/Issi4.png new file mode 100644 index 00000000..8acd4ceb Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Issi4.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Issi7.png b/[core]/mercyv-garage/nui/images/cars/Issi7.png new file mode 100644 index 00000000..f4ae783c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Issi7.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Italigtb.png b/[core]/mercyv-garage/nui/images/cars/Italigtb.png new file mode 100644 index 00000000..064b70e0 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Italigtb.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Italigtb2.png b/[core]/mercyv-garage/nui/images/cars/Italigtb2.png new file mode 100644 index 00000000..5e2226ce Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Italigtb2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Italigto.png b/[core]/mercyv-garage/nui/images/cars/Italigto.png new file mode 100644 index 00000000..58e17332 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Italigto.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Jackal.png b/[core]/mercyv-garage/nui/images/cars/Jackal.png new file mode 100644 index 00000000..adcb6fed Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Jackal.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Jb700.png b/[core]/mercyv-garage/nui/images/cars/Jb700.png new file mode 100644 index 00000000..751bac3f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Jb700.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Jb7002.png b/[core]/mercyv-garage/nui/images/cars/Jb7002.png new file mode 100644 index 00000000..4deca4eb Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Jb7002.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Jester.png b/[core]/mercyv-garage/nui/images/cars/Jester.png new file mode 100644 index 00000000..946298a4 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Jester.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Jester2.png b/[core]/mercyv-garage/nui/images/cars/Jester2.png new file mode 100644 index 00000000..87c10d06 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Jester2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Jugular.png b/[core]/mercyv-garage/nui/images/cars/Jugular.png new file mode 100644 index 00000000..d15d8d85 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Jugular.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Kalahari.png b/[core]/mercyv-garage/nui/images/cars/Kalahari.png new file mode 100644 index 00000000..85d95be5 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Kalahari.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Kamacho.png b/[core]/mercyv-garage/nui/images/cars/Kamacho.png new file mode 100644 index 00000000..bd97ea8f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Kamacho.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Kanjo.png b/[core]/mercyv-garage/nui/images/cars/Kanjo.png new file mode 100644 index 00000000..9d8a8eea Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Kanjo.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Khamelion.png b/[core]/mercyv-garage/nui/images/cars/Khamelion.png new file mode 100644 index 00000000..7078435d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Khamelion.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Komoda.png b/[core]/mercyv-garage/nui/images/cars/Komoda.png new file mode 100644 index 00000000..5fde3594 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Komoda.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Krieger.png b/[core]/mercyv-garage/nui/images/cars/Krieger.png new file mode 100644 index 00000000..51c136b7 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Krieger.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Kuruma.png b/[core]/mercyv-garage/nui/images/cars/Kuruma.png new file mode 100644 index 00000000..5dbb5276 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Kuruma.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Landstalker.png b/[core]/mercyv-garage/nui/images/cars/Landstalker.png new file mode 100644 index 00000000..a0dcad73 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Landstalker.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Landstalker2.png b/[core]/mercyv-garage/nui/images/cars/Landstalker2.png new file mode 100644 index 00000000..129c9be5 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Landstalker2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Le7b.png b/[core]/mercyv-garage/nui/images/cars/Le7b.png new file mode 100644 index 00000000..61d6ad78 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Le7b.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Lectro.png b/[core]/mercyv-garage/nui/images/cars/Lectro.png new file mode 100644 index 00000000..5778f2d9 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Lectro.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Locust.png b/[core]/mercyv-garage/nui/images/cars/Locust.png new file mode 100644 index 00000000..66e57d5a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Locust.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Lurcher.png b/[core]/mercyv-garage/nui/images/cars/Lurcher.png new file mode 100644 index 00000000..ed2e59b3 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Lurcher.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Lynx.png b/[core]/mercyv-garage/nui/images/cars/Lynx.png new file mode 100644 index 00000000..5aaae99f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Lynx.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Mamba.png b/[core]/mercyv-garage/nui/images/cars/Mamba.png new file mode 100644 index 00000000..b2ac0fc2 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Mamba.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Manana.png b/[core]/mercyv-garage/nui/images/cars/Manana.png new file mode 100644 index 00000000..64007625 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Manana.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Manana2.png b/[core]/mercyv-garage/nui/images/cars/Manana2.png new file mode 100644 index 00000000..e97295e3 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Manana2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Massacro.png b/[core]/mercyv-garage/nui/images/cars/Massacro.png new file mode 100644 index 00000000..eaaf108b Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Massacro.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Massacro2.png b/[core]/mercyv-garage/nui/images/cars/Massacro2.png new file mode 100644 index 00000000..c4e4e91c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Massacro2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Michelli.png b/[core]/mercyv-garage/nui/images/cars/Michelli.png new file mode 100644 index 00000000..d280c4b9 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Michelli.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Monroe.png b/[core]/mercyv-garage/nui/images/cars/Monroe.png new file mode 100644 index 00000000..9eec4839 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Monroe.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Moonbeam.png b/[core]/mercyv-garage/nui/images/cars/Moonbeam.png new file mode 100644 index 00000000..969ca280 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Moonbeam.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Nebula.png b/[core]/mercyv-garage/nui/images/cars/Nebula.png new file mode 100644 index 00000000..4a5b9ec0 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Nebula.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Nemesis.png b/[core]/mercyv-garage/nui/images/cars/Nemesis.png new file mode 100644 index 00000000..c59fadb7 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Nemesis.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Neo.png b/[core]/mercyv-garage/nui/images/cars/Neo.png new file mode 100644 index 00000000..108f5988 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Neo.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Neon.png b/[core]/mercyv-garage/nui/images/cars/Neon.png new file mode 100644 index 00000000..0214c261 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Neon.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Nero.png b/[core]/mercyv-garage/nui/images/cars/Nero.png new file mode 100644 index 00000000..12eab663 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Nero.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Nero2.png b/[core]/mercyv-garage/nui/images/cars/Nero2.png new file mode 100644 index 00000000..c9902036 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Nero2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Nightblade.png b/[core]/mercyv-garage/nui/images/cars/Nightblade.png new file mode 100644 index 00000000..67bfe7c4 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Nightblade.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Nightshade.png b/[core]/mercyv-garage/nui/images/cars/Nightshade.png new file mode 100644 index 00000000..0efcd6f4 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Nightshade.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Ninef.png b/[core]/mercyv-garage/nui/images/cars/Ninef.png new file mode 100644 index 00000000..fa0cd5cf Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Ninef.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Ninef2.png b/[core]/mercyv-garage/nui/images/cars/Ninef2.png new file mode 100644 index 00000000..73d18d90 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Ninef2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Novak.png b/[core]/mercyv-garage/nui/images/cars/Novak.png new file mode 100644 index 00000000..0a62f0ee Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Novak.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Omnis.png b/[core]/mercyv-garage/nui/images/cars/Omnis.png new file mode 100644 index 00000000..23517f6d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Omnis.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Oppressor.png b/[core]/mercyv-garage/nui/images/cars/Oppressor.png new file mode 100644 index 00000000..a1c186ae Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Oppressor.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Oracle2.png b/[core]/mercyv-garage/nui/images/cars/Oracle2.png new file mode 100644 index 00000000..f928314f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Oracle2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Osiris.png b/[core]/mercyv-garage/nui/images/cars/Osiris.png new file mode 100644 index 00000000..e2a11f27 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Osiris.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Outlaw.png b/[core]/mercyv-garage/nui/images/cars/Outlaw.png new file mode 100644 index 00000000..0b7591e1 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Outlaw.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Panto.png b/[core]/mercyv-garage/nui/images/cars/Panto.png new file mode 100644 index 00000000..faa86236 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Panto.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Paragon.png b/[core]/mercyv-garage/nui/images/cars/Paragon.png new file mode 100644 index 00000000..c4cc7446 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Paragon.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Paragon2.png b/[core]/mercyv-garage/nui/images/cars/Paragon2.png new file mode 100644 index 00000000..a5f3a0d4 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Paragon2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Pariah.png b/[core]/mercyv-garage/nui/images/cars/Pariah.png new file mode 100644 index 00000000..51d9d34c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Pariah.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Patriot.png b/[core]/mercyv-garage/nui/images/cars/Patriot.png new file mode 100644 index 00000000..992114fb Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Patriot.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Patriot2.png b/[core]/mercyv-garage/nui/images/cars/Patriot2.png new file mode 100644 index 00000000..753f2086 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Patriot2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Pcj.png b/[core]/mercyv-garage/nui/images/cars/Pcj.png new file mode 100644 index 00000000..59707c52 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Pcj.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Penetrator.png b/[core]/mercyv-garage/nui/images/cars/Penetrator.png new file mode 100644 index 00000000..638fcdd0 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Penetrator.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Penumbra.png b/[core]/mercyv-garage/nui/images/cars/Penumbra.png new file mode 100644 index 00000000..25d65ac4 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Penumbra.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Penumbra2.png b/[core]/mercyv-garage/nui/images/cars/Penumbra2.png new file mode 100644 index 00000000..f3760406 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Penumbra2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Peyote.png b/[core]/mercyv-garage/nui/images/cars/Peyote.png new file mode 100644 index 00000000..94069903 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Peyote.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Peyote2.png b/[core]/mercyv-garage/nui/images/cars/Peyote2.png new file mode 100644 index 00000000..9e4ef149 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Peyote2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Pfister811.png b/[core]/mercyv-garage/nui/images/cars/Pfister811.png new file mode 100644 index 00000000..6036327c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Pfister811.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Phoenix.png b/[core]/mercyv-garage/nui/images/cars/Phoenix.png new file mode 100644 index 00000000..93dee990 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Phoenix.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Pigalle.png b/[core]/mercyv-garage/nui/images/cars/Pigalle.png new file mode 100644 index 00000000..44301d3a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Pigalle.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Prairie.png b/[core]/mercyv-garage/nui/images/cars/Prairie.png new file mode 100644 index 00000000..71616e27 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Prairie.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Premier.png b/[core]/mercyv-garage/nui/images/cars/Premier.png new file mode 100644 index 00000000..7bec001b Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Premier.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Primo.png b/[core]/mercyv-garage/nui/images/cars/Primo.png new file mode 100644 index 00000000..07267e39 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Primo.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Primo2.png b/[core]/mercyv-garage/nui/images/cars/Primo2.png new file mode 100644 index 00000000..0b63f5a9 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Primo2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Radi.png b/[core]/mercyv-garage/nui/images/cars/Radi.png new file mode 100644 index 00000000..3e4008cb Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Radi.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Raiden.png b/[core]/mercyv-garage/nui/images/cars/Raiden.png new file mode 100644 index 00000000..132ee3f0 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Raiden.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Rancherxl.png b/[core]/mercyv-garage/nui/images/cars/Rancherxl.png new file mode 100644 index 00000000..7546cd6a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Rancherxl.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Rapidgt.png b/[core]/mercyv-garage/nui/images/cars/Rapidgt.png new file mode 100644 index 00000000..760daccd Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Rapidgt.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Rapidgt2.png b/[core]/mercyv-garage/nui/images/cars/Rapidgt2.png new file mode 100644 index 00000000..17c46142 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Rapidgt2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Rapidgt3.png b/[core]/mercyv-garage/nui/images/cars/Rapidgt3.png new file mode 100644 index 00000000..aab51669 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Rapidgt3.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Raptor.png b/[core]/mercyv-garage/nui/images/cars/Raptor.png new file mode 100644 index 00000000..8cc1d6b7 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Raptor.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Ratbike.png b/[core]/mercyv-garage/nui/images/cars/Ratbike.png new file mode 100644 index 00000000..4b090fba Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Ratbike.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Reaper.png b/[core]/mercyv-garage/nui/images/cars/Reaper.png new file mode 100644 index 00000000..15790b3a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Reaper.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Rebel2.png b/[core]/mercyv-garage/nui/images/cars/Rebel2.png new file mode 100644 index 00000000..727c96ce Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Rebel2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Rebla.png b/[core]/mercyv-garage/nui/images/cars/Rebla.png new file mode 100644 index 00000000..8ba4f682 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Rebla.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Regina.png b/[core]/mercyv-garage/nui/images/cars/Regina.png new file mode 100644 index 00000000..3ae28afe Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Regina.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Rentalbus.png b/[core]/mercyv-garage/nui/images/cars/Rentalbus.png new file mode 100644 index 00000000..c6985785 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Rentalbus.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Retinue.png b/[core]/mercyv-garage/nui/images/cars/Retinue.png new file mode 100644 index 00000000..080dd33e Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Retinue.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Retinue2.png b/[core]/mercyv-garage/nui/images/cars/Retinue2.png new file mode 100644 index 00000000..4c10b52c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Retinue2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Revolter.png b/[core]/mercyv-garage/nui/images/cars/Revolter.png new file mode 100644 index 00000000..84100e33 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Revolter.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Rhapsody.png b/[core]/mercyv-garage/nui/images/cars/Rhapsody.png new file mode 100644 index 00000000..3b878784 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Rhapsody.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Riata.png b/[core]/mercyv-garage/nui/images/cars/Riata.png new file mode 100644 index 00000000..be8d93d5 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Riata.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Rocoto.png b/[core]/mercyv-garage/nui/images/cars/Rocoto.png new file mode 100644 index 00000000..6e6f6b98 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Rocoto.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Ruffian.png b/[core]/mercyv-garage/nui/images/cars/Ruffian.png new file mode 100644 index 00000000..3ffdf948 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Ruffian.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Ruiner2.png b/[core]/mercyv-garage/nui/images/cars/Ruiner2.png new file mode 100644 index 00000000..67899ec3 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Ruiner2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Ruston.png b/[core]/mercyv-garage/nui/images/cars/Ruston.png new file mode 100644 index 00000000..883d7b54 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Ruston.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/S80.png b/[core]/mercyv-garage/nui/images/cars/S80.png new file mode 100644 index 00000000..80cd6463 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/S80.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Sabregt.png b/[core]/mercyv-garage/nui/images/cars/Sabregt.png new file mode 100644 index 00000000..e5ec47ab Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Sabregt.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Sabregt2.png b/[core]/mercyv-garage/nui/images/cars/Sabregt2.png new file mode 100644 index 00000000..d9c485cb Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Sabregt2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Sadler.png b/[core]/mercyv-garage/nui/images/cars/Sadler.png new file mode 100644 index 00000000..364738f2 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Sadler.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Sadler2.png b/[core]/mercyv-garage/nui/images/cars/Sadler2.png new file mode 100644 index 00000000..3e1152e5 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Sadler2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Sanchez.png b/[core]/mercyv-garage/nui/images/cars/Sanchez.png new file mode 100644 index 00000000..b24e0fae Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Sanchez.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Sanctus.png b/[core]/mercyv-garage/nui/images/cars/Sanctus.png new file mode 100644 index 00000000..c84ce1da Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Sanctus.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Sandking.png b/[core]/mercyv-garage/nui/images/cars/Sandking.png new file mode 100644 index 00000000..41adc2f2 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Sandking.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Savestra.png b/[core]/mercyv-garage/nui/images/cars/Savestra.png new file mode 100644 index 00000000..7bfb5cc0 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Savestra.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Sc1.png b/[core]/mercyv-garage/nui/images/cars/Sc1.png new file mode 100644 index 00000000..f3276c95 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Sc1.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Schafter2.png b/[core]/mercyv-garage/nui/images/cars/Schafter2.png new file mode 100644 index 00000000..2f013f07 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Schafter2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Schlagen.png b/[core]/mercyv-garage/nui/images/cars/Schlagen.png new file mode 100644 index 00000000..dacc6269 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Schlagen.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Schwarzer.png b/[core]/mercyv-garage/nui/images/cars/Schwarzer.png new file mode 100644 index 00000000..1aae965a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Schwarzer.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Seminole.png b/[core]/mercyv-garage/nui/images/cars/Seminole.png new file mode 100644 index 00000000..a50218cb Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Seminole.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Sentinel2.png b/[core]/mercyv-garage/nui/images/cars/Sentinel2.png new file mode 100644 index 00000000..6bffe369 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Sentinel2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Serrano.png b/[core]/mercyv-garage/nui/images/cars/Serrano.png new file mode 100644 index 00000000..89648e0a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Serrano.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Seven70.png b/[core]/mercyv-garage/nui/images/cars/Seven70.png new file mode 100644 index 00000000..4f3be04b Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Seven70.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Shotaro.png b/[core]/mercyv-garage/nui/images/cars/Shotaro.png new file mode 100644 index 00000000..4ceacec3 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Shotaro.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Sovereign.png b/[core]/mercyv-garage/nui/images/cars/Sovereign.png new file mode 100644 index 00000000..a3211237 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Sovereign.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Specter.png b/[core]/mercyv-garage/nui/images/cars/Specter.png new file mode 100644 index 00000000..b4ba1048 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Specter.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Specter2.png b/[core]/mercyv-garage/nui/images/cars/Specter2.png new file mode 100644 index 00000000..49c85ecd Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Specter2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Stafford.png b/[core]/mercyv-garage/nui/images/cars/Stafford.png new file mode 100644 index 00000000..91051d80 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Stafford.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Stalion.png b/[core]/mercyv-garage/nui/images/cars/Stalion.png new file mode 100644 index 00000000..da6e13fd Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Stalion.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Stalion2.png b/[core]/mercyv-garage/nui/images/cars/Stalion2.png new file mode 100644 index 00000000..c38f5397 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Stalion2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Stanier.png b/[core]/mercyv-garage/nui/images/cars/Stanier.png new file mode 100644 index 00000000..67a68a86 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Stanier.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Stinger.png b/[core]/mercyv-garage/nui/images/cars/Stinger.png new file mode 100644 index 00000000..ab3159ba Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Stinger.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Stingergt.png b/[core]/mercyv-garage/nui/images/cars/Stingergt.png new file mode 100644 index 00000000..45fc718d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Stingergt.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Streiter.png b/[core]/mercyv-garage/nui/images/cars/Streiter.png new file mode 100644 index 00000000..db4b924f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Streiter.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Stretch.png b/[core]/mercyv-garage/nui/images/cars/Stretch.png new file mode 100644 index 00000000..0b160adb Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Stretch.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Stromberg.png b/[core]/mercyv-garage/nui/images/cars/Stromberg.png new file mode 100644 index 00000000..61b2dcd2 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Stromberg.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Sugoi.png b/[core]/mercyv-garage/nui/images/cars/Sugoi.png new file mode 100644 index 00000000..46ffa0a2 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Sugoi.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Sultan.png b/[core]/mercyv-garage/nui/images/cars/Sultan.png new file mode 100644 index 00000000..22769b68 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Sultan.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Sultan2.png b/[core]/mercyv-garage/nui/images/cars/Sultan2.png new file mode 100644 index 00000000..6fab0d49 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Sultan2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Sultanrs.png b/[core]/mercyv-garage/nui/images/cars/Sultanrs.png new file mode 100644 index 00000000..f68a5710 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Sultanrs.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Superd.png b/[core]/mercyv-garage/nui/images/cars/Superd.png new file mode 100644 index 00000000..acf6779f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Superd.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Surano.png b/[core]/mercyv-garage/nui/images/cars/Surano.png new file mode 100644 index 00000000..019962a6 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Surano.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Surge.png b/[core]/mercyv-garage/nui/images/cars/Surge.png new file mode 100644 index 00000000..c77c4c93 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Surge.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Swinger.png b/[core]/mercyv-garage/nui/images/cars/Swinger.png new file mode 100644 index 00000000..16000e84 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Swinger.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/T20.png b/[core]/mercyv-garage/nui/images/cars/T20.png new file mode 100644 index 00000000..e3c64270 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/T20.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Tailgater.png b/[core]/mercyv-garage/nui/images/cars/Tailgater.png new file mode 100644 index 00000000..1a793ff6 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Tailgater.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Taipan.png b/[core]/mercyv-garage/nui/images/cars/Taipan.png new file mode 100644 index 00000000..add8810a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Taipan.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Tampa.png b/[core]/mercyv-garage/nui/images/cars/Tampa.png new file mode 100644 index 00000000..48cc44ae Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Tampa.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Tempesta.png b/[core]/mercyv-garage/nui/images/cars/Tempesta.png new file mode 100644 index 00000000..d5227cea Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Tempesta.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Tezeract.png b/[core]/mercyv-garage/nui/images/cars/Tezeract.png new file mode 100644 index 00000000..f9581f4a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Tezeract.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Thrax.png b/[core]/mercyv-garage/nui/images/cars/Thrax.png new file mode 100644 index 00000000..cf4c57ad Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Thrax.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Thrust.png b/[core]/mercyv-garage/nui/images/cars/Thrust.png new file mode 100644 index 00000000..67c7b330 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Thrust.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Tigon.png b/[core]/mercyv-garage/nui/images/cars/Tigon.png new file mode 100644 index 00000000..a6848de0 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Tigon.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Torero.png b/[core]/mercyv-garage/nui/images/cars/Torero.png new file mode 100644 index 00000000..be94b4cd Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Torero.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Tornado.png b/[core]/mercyv-garage/nui/images/cars/Tornado.png new file mode 100644 index 00000000..889f2f29 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Tornado.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/Toros.png b/[core]/mercyv-garage/nui/images/cars/Toros.png new file mode 100644 index 00000000..0c67964e Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/Toros.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/cogcabri.png b/[core]/mercyv-garage/nui/images/cars/cogcabri.png new file mode 100644 index 00000000..96c476f6 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/cogcabri.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/dominator.png b/[core]/mercyv-garage/nui/images/cars/dominator.png new file mode 100644 index 00000000..c46206b9 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/dominator.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/dominator2.png b/[core]/mercyv-garage/nui/images/cars/dominator2.png new file mode 100644 index 00000000..f0779d86 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/dominator2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/dominator3.png b/[core]/mercyv-garage/nui/images/cars/dominator3.png new file mode 100644 index 00000000..cd85227d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/dominator3.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/gstsentgts2.png b/[core]/mercyv-garage/nui/images/cars/gstsentgts2.png new file mode 100644 index 00000000..223bc4c0 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/gstsentgts2.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/noimage.png b/[core]/mercyv-garage/nui/images/cars/noimage.png new file mode 100644 index 00000000..46dfad72 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/noimage.png differ diff --git a/[core]/mercyv-garage/nui/images/cars/porschepanemara.png b/[core]/mercyv-garage/nui/images/cars/porschepanemara.png new file mode 100644 index 00000000..93e1c32d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/cars/porschepanemara.png differ diff --git a/[core]/mercyv-garage/nui/images/category.png b/[core]/mercyv-garage/nui/images/category.png new file mode 100644 index 00000000..c202382e Binary files /dev/null and b/[core]/mercyv-garage/nui/images/category.png differ diff --git a/[core]/mercyv-garage/nui/images/close.png b/[core]/mercyv-garage/nui/images/close.png new file mode 100644 index 00000000..71777bb5 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/close.png differ diff --git a/[core]/mercyv-garage/nui/images/defaultimage.png b/[core]/mercyv-garage/nui/images/defaultimage.png new file mode 100644 index 00000000..3b794ce6 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/defaultimage.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/#U00d6verfl#U00f6d.png b/[core]/mercyv-garage/nui/images/logo/#U00d6verfl#U00f6d.png new file mode 100644 index 00000000..a6db9374 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/#U00d6verfl#U00f6d.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Albany.png b/[core]/mercyv-garage/nui/images/logo/Albany.png new file mode 100644 index 00000000..216ae4d1 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Albany.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Annis.png b/[core]/mercyv-garage/nui/images/logo/Annis.png new file mode 100644 index 00000000..a23af75e Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Annis.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/B#U00fcrgerfahrzeug.png b/[core]/mercyv-garage/nui/images/logo/B#U00fcrgerfahrzeug.png new file mode 100644 index 00000000..3bc6475a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/B#U00fcrgerfahrzeug.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/BF.png b/[core]/mercyv-garage/nui/images/logo/BF.png new file mode 100644 index 00000000..99d8f15d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/BF.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Benefactor.png b/[core]/mercyv-garage/nui/images/logo/Benefactor.png new file mode 100644 index 00000000..ce1efb72 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Benefactor.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Bollokan.png b/[core]/mercyv-garage/nui/images/logo/Bollokan.png new file mode 100644 index 00000000..bfe0b419 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Bollokan.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Bravado.png b/[core]/mercyv-garage/nui/images/logo/Bravado.png new file mode 100644 index 00000000..6e38f237 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Bravado.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Brute.png b/[core]/mercyv-garage/nui/images/logo/Brute.png new file mode 100644 index 00000000..4af696b5 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Brute.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Buckingham.png b/[core]/mercyv-garage/nui/images/logo/Buckingham.png new file mode 100644 index 00000000..37029a2d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Buckingham.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Canis.png b/[core]/mercyv-garage/nui/images/logo/Canis.png new file mode 100644 index 00000000..b8715529 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Canis.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Chariot.png b/[core]/mercyv-garage/nui/images/logo/Chariot.png new file mode 100644 index 00000000..02cd51fa Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Chariot.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Cheval.png b/[core]/mercyv-garage/nui/images/logo/Cheval.png new file mode 100644 index 00000000..b8b8b80d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Cheval.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Classique.png b/[core]/mercyv-garage/nui/images/logo/Classique.png new file mode 100644 index 00000000..04057f1d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Classique.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Coil.png b/[core]/mercyv-garage/nui/images/logo/Coil.png new file mode 100644 index 00000000..49ca240a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Coil.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Custom.png b/[core]/mercyv-garage/nui/images/logo/Custom.png new file mode 100644 index 00000000..b8715529 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Custom.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/DUDE-Logo.png b/[core]/mercyv-garage/nui/images/logo/DUDE-Logo.png new file mode 100644 index 00000000..a94fcbc2 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/DUDE-Logo.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Declasse.png b/[core]/mercyv-garage/nui/images/logo/Declasse.png new file mode 100644 index 00000000..193bea53 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Declasse.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Dewbauchee.png b/[core]/mercyv-garage/nui/images/logo/Dewbauchee.png new file mode 100644 index 00000000..6156dd4c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Dewbauchee.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Dinka.png b/[core]/mercyv-garage/nui/images/logo/Dinka.png new file mode 100644 index 00000000..ae0de4d9 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Dinka.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Dundreary.png b/[core]/mercyv-garage/nui/images/logo/Dundreary.png new file mode 100644 index 00000000..1fac4ce3 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Dundreary.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Emperor.png b/[core]/mercyv-garage/nui/images/logo/Emperor.png new file mode 100644 index 00000000..30d7eea3 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Emperor.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Enus.png b/[core]/mercyv-garage/nui/images/logo/Enus.png new file mode 100644 index 00000000..8603f684 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Enus.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Fathom.png b/[core]/mercyv-garage/nui/images/logo/Fathom.png new file mode 100644 index 00000000..81e634ff Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Fathom.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Gallivanter.png b/[core]/mercyv-garage/nui/images/logo/Gallivanter.png new file mode 100644 index 00000000..6d058ae0 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Gallivanter.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Grotti.png b/[core]/mercyv-garage/nui/images/logo/Grotti.png new file mode 100644 index 00000000..803eddfc Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Grotti.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/HVY.png b/[core]/mercyv-garage/nui/images/logo/HVY.png new file mode 100644 index 00000000..625eb30f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/HVY.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Hijak.png b/[core]/mercyv-garage/nui/images/logo/Hijak.png new file mode 100644 index 00000000..b3c40670 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Hijak.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Imponte.png b/[core]/mercyv-garage/nui/images/logo/Imponte.png new file mode 100644 index 00000000..62d422f6 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Imponte.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Invetero.png b/[core]/mercyv-garage/nui/images/logo/Invetero.png new file mode 100644 index 00000000..b885726a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Invetero.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Jack Sheepe.png b/[core]/mercyv-garage/nui/images/logo/Jack Sheepe.png new file mode 100644 index 00000000..5185b8ff Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Jack Sheepe.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/JackSheepe.png b/[core]/mercyv-garage/nui/images/logo/JackSheepe.png new file mode 100644 index 00000000..cac15dc5 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/JackSheepe.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Jobuilt.png b/[core]/mercyv-garage/nui/images/logo/Jobuilt.png new file mode 100644 index 00000000..7a1a0a92 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Jobuilt.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Karin.png b/[core]/mercyv-garage/nui/images/logo/Karin.png new file mode 100644 index 00000000..e16212a4 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Karin.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/LCC.png b/[core]/mercyv-garage/nui/images/logo/LCC.png new file mode 100644 index 00000000..c40c6c9a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/LCC.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/LCS.png b/[core]/mercyv-garage/nui/images/logo/LCS.png new file mode 100644 index 00000000..d0acde3c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/LCS.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Lampadati.png b/[core]/mercyv-garage/nui/images/logo/Lampadati.png new file mode 100644 index 00000000..52fd417a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Lampadati.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Liberty Chop Shop.png b/[core]/mercyv-garage/nui/images/logo/Liberty Chop Shop.png new file mode 100644 index 00000000..22e57644 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Liberty Chop Shop.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Liberty City Cycles.png b/[core]/mercyv-garage/nui/images/logo/Liberty City Cycles.png new file mode 100644 index 00000000..c5c4f6d8 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Liberty City Cycles.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/MTL.png b/[core]/mercyv-garage/nui/images/logo/MTL.png new file mode 100644 index 00000000..ccd1e48d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/MTL.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Maibatsu Corporation.png b/[core]/mercyv-garage/nui/images/logo/Maibatsu Corporation.png new file mode 100644 index 00000000..bff2249b Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Maibatsu Corporation.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Maibatsu.png b/[core]/mercyv-garage/nui/images/logo/Maibatsu.png new file mode 100644 index 00000000..b928ded0 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Maibatsu.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Mammoth.png b/[core]/mercyv-garage/nui/images/logo/Mammoth.png new file mode 100644 index 00000000..0b6d022c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Mammoth.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Maxwell.png b/[core]/mercyv-garage/nui/images/logo/Maxwell.png new file mode 100644 index 00000000..43a1f649 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Maxwell.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Nagasaki.png b/[core]/mercyv-garage/nui/images/logo/Nagasaki.png new file mode 100644 index 00000000..510fe0f2 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Nagasaki.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Obey.png b/[core]/mercyv-garage/nui/images/logo/Obey.png new file mode 100644 index 00000000..b64c1bea Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Obey.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Ocelot.png b/[core]/mercyv-garage/nui/images/logo/Ocelot.png new file mode 100644 index 00000000..167d306b Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Ocelot.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Overflod.png b/[core]/mercyv-garage/nui/images/logo/Overflod.png new file mode 100644 index 00000000..0a0e5a35 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Overflod.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Pegassi.png b/[core]/mercyv-garage/nui/images/logo/Pegassi.png new file mode 100644 index 00000000..6a1d4c91 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Pegassi.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Pfister.png b/[core]/mercyv-garage/nui/images/logo/Pfister.png new file mode 100644 index 00000000..ef66fb93 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Pfister.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Principe.png b/[core]/mercyv-garage/nui/images/logo/Principe.png new file mode 100644 index 00000000..2404d16e Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Principe.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/ProLaps.png b/[core]/mercyv-garage/nui/images/logo/ProLaps.png new file mode 100644 index 00000000..187fbd03 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/ProLaps.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Progen.png b/[core]/mercyv-garage/nui/images/logo/Progen.png new file mode 100644 index 00000000..6715be84 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Progen.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/RUNE.png b/[core]/mercyv-garage/nui/images/logo/RUNE.png new file mode 100644 index 00000000..54facf62 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/RUNE.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Schyster.png b/[core]/mercyv-garage/nui/images/logo/Schyster.png new file mode 100644 index 00000000..8aaa860c Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Schyster.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Shitzu.png b/[core]/mercyv-garage/nui/images/logo/Shitzu.png new file mode 100644 index 00000000..5b252c2a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Shitzu.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Speedophile.png b/[core]/mercyv-garage/nui/images/logo/Speedophile.png new file mode 100644 index 00000000..3dd01785 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Speedophile.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Stanley.png b/[core]/mercyv-garage/nui/images/logo/Stanley.png new file mode 100644 index 00000000..26ab099d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Stanley.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Steel Horse.png b/[core]/mercyv-garage/nui/images/logo/Steel Horse.png new file mode 100644 index 00000000..368b247f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Steel Horse.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/SteelHorse.png b/[core]/mercyv-garage/nui/images/logo/SteelHorse.png new file mode 100644 index 00000000..40e63a9f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/SteelHorse.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Truffade.png b/[core]/mercyv-garage/nui/images/logo/Truffade.png new file mode 100644 index 00000000..c09bfe2f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Truffade.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Vapid.png b/[core]/mercyv-garage/nui/images/logo/Vapid.png new file mode 100644 index 00000000..ca53a240 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Vapid.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Vulcar.png b/[core]/mercyv-garage/nui/images/logo/Vulcar.png new file mode 100644 index 00000000..b4b857a2 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Vulcar.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Vysser.png b/[core]/mercyv-garage/nui/images/logo/Vysser.png new file mode 100644 index 00000000..7cde65a5 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Vysser.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/WMC.png b/[core]/mercyv-garage/nui/images/logo/WMC.png new file mode 100644 index 00000000..3af5ed8a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/WMC.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Weeny.png b/[core]/mercyv-garage/nui/images/logo/Weeny.png new file mode 100644 index 00000000..2a60d244 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Weeny.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Western Motorcycle Company.png b/[core]/mercyv-garage/nui/images/logo/Western Motorcycle Company.png new file mode 100644 index 00000000..ab97689a Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Western Motorcycle Company.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Western.png b/[core]/mercyv-garage/nui/images/logo/Western.png new file mode 100644 index 00000000..39ea89cf Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Western.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/WesternCompany.png b/[core]/mercyv-garage/nui/images/logo/WesternCompany.png new file mode 100644 index 00000000..39ea89cf Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/WesternCompany.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Willard.png b/[core]/mercyv-garage/nui/images/logo/Willard.png new file mode 100644 index 00000000..e0b9f604 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Willard.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/Zirconium.png b/[core]/mercyv-garage/nui/images/logo/Zirconium.png new file mode 100644 index 00000000..1c4a9be8 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/Zirconium.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/ambulance.png b/[core]/mercyv-garage/nui/images/logo/ambulance.png new file mode 100644 index 00000000..6a01298d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/ambulance.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/bennys.png b/[core]/mercyv-garage/nui/images/logo/bennys.png new file mode 100644 index 00000000..fedb1c0b Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/bennys.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/bmv.png b/[core]/mercyv-garage/nui/images/logo/bmv.png new file mode 100644 index 00000000..f54326ec Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/bmv.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/ems.png b/[core]/mercyv-garage/nui/images/logo/ems.png new file mode 100644 index 00000000..6a01298d Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/ems.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/logo.png b/[core]/mercyv-garage/nui/images/logo/logo.png new file mode 100644 index 00000000..ea282444 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/logo.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/lscustom.png b/[core]/mercyv-garage/nui/images/logo/lscustom.png new file mode 100644 index 00000000..17115eb1 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/lscustom.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/lscustoms.png b/[core]/mercyv-garage/nui/images/logo/lscustoms.png new file mode 100644 index 00000000..17115eb1 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/lscustoms.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/police.png b/[core]/mercyv-garage/nui/images/logo/police.png new file mode 100644 index 00000000..b762c293 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/police.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/police2.png b/[core]/mercyv-garage/nui/images/logo/police2.png new file mode 100644 index 00000000..b762c293 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/police2.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/ubermacht.png b/[core]/mercyv-garage/nui/images/logo/ubermacht.png new file mode 100644 index 00000000..2f55db2f Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/ubermacht.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/uebermacht.png b/[core]/mercyv-garage/nui/images/logo/uebermacht.png new file mode 100644 index 00000000..6c05cba5 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/uebermacht.png differ diff --git a/[core]/mercyv-garage/nui/images/logo/unmarked.png b/[core]/mercyv-garage/nui/images/logo/unmarked.png new file mode 100644 index 00000000..980a98cd Binary files /dev/null and b/[core]/mercyv-garage/nui/images/logo/unmarked.png differ diff --git a/[core]/mercyv-garage/nui/images/rectangle.png b/[core]/mercyv-garage/nui/images/rectangle.png new file mode 100644 index 00000000..855956e0 Binary files /dev/null and b/[core]/mercyv-garage/nui/images/rectangle.png differ diff --git a/[core]/mercyv-garage/nui/index.html b/[core]/mercyv-garage/nui/index.html new file mode 100644 index 00000000..00d64de0 --- /dev/null +++ b/[core]/mercyv-garage/nui/index.html @@ -0,0 +1,319 @@ + + + + + + MercyV Garage + + + + + + + +
+ + +
+
+ + +
+
+
+ MercyV +
+
+ MercyV Garage + {{ garageId || 'Garage' }} +
+
+
+
{{ playerName }}
+ +
+
+ + +
+ + +
+
+ Alle +
+
+ Autos +
+
+ Moto +
+
+ Bikes +
+
+ Boote +
+
+ Flug +
+
+
+ Favoriten +
+
+
+ Admin +
+
+ + +
+ + +
+
+ + +
+
+ Abholgebühr: {{ impoundPrice }}$ +
+
+ + +
+ + +
+
+
+ {{ v.nearby ? 'In der Nähe' : v.stored===0 ? 'Draußen' : 'Eingelagert' }} +
+ +
{{ v.carname }}
+
{{ v.plate }}
+
+ +
+
+
+ +

Keine Fahrzeuge

+
+
+ + +
+
+ +
+
{{ selectedVehicle.carname }}
+
{{ selectedVehicle.plate }}
+
{{ selectedVehicle.parking }}
+
+ + {{ selectedVehicle.stored===0 ? 'Aktuell draußen' : 'Eingelagert' }} +
+ + +
+ Fahrzeug ist draußen +
+
+ + {{ selectedVehicle.favorite===1 ? 'Favorit entfernen' : 'Zu Favoriten' }} +
+
+ +
+
+
+
+
+ + +
+
+ +
+
+
+ +
+
+ Garage Editor + Admin Tool +
+
+
+ + +
+
+ +
+ +
+
+ Garagen ({{ adminGarages.length }}) +
+
+
+
+ +
+
+
{{ g.label }}
+
{{ g.type }}
+
+
+ + +
+
+
+
+ +
+
+
+ {{ editingGarage._isNew ? 'Neue Garage erstellen' : 'Bearbeiten: ' + editingGarage.label }} +
+ +
+
+
+
Basis
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ Positionen + Position aufsuchen → "Hier erfassen" → E drücken +
+
+
{{ pos.label }}
+
+ + + + +
+ +
+
+ + +
+
+ Job-Fahrzeuge + GTA-Modellname z.B. police3, ambulance +
+
+
+ {{ v.model }} + +
+
+ Noch keine Fahrzeuge eingetragen +
+
+
+ + +
+
+ +
+
+ +
+ +

Garage auswählen oder neue erstellen

+
+ +
+
+
+ + +
+
+ +

Garage löschen?

+

{{ deleteConfirmId }} wird permanent entfernt.

+
+ + +
+
+
+ + +
+
+ +

Position erfassen

+

{{ getFieldLabel(capturingField) }}

+

Geh zur Position → E drücken

+

ESC = Abbrechen

+
+
+ +
+ + + + diff --git a/[core]/mercyv-garage/nui/script.js b/[core]/mercyv-garage/nui/script.js new file mode 100644 index 00000000..4f1e861c --- /dev/null +++ b/[core]/mercyv-garage/nui/script.js @@ -0,0 +1,226 @@ +const app = new Vue({ + el: '#app', + data: { + show: false, showAdminPanel: false, isAdmin: false, + garageId: null, garageType: 'normal', + playerName: '', avatar: 'images/defaultimage.png', + vehicles: [], activeTab: 'all', showFavorites: false, + searchQuery: '', + selectedVehicle: null, selectedIndex: null, + impound: false, impoundPrice: 1500, + adminGarages: [], editingGarage: null, + deleteConfirmId: null, capturingField: null, + windowWidth: window.innerWidth, + posFields: [ + { field: 'npc', label: 'NPC Position', hasHeading: true }, + { field: 'spawn', label: 'Spawn Position (Fahrzeug erscheint)', hasHeading: true }, + { field: 'park', label: 'Einpark-Zone (Marker am Boden)', hasHeading: false }, + ], + jobVehicles: [], // Fahrzeuge für die aktuelle Jobgarage + newJobVehicle: '', // Eingabe: neues Modell + }, + + computed: { + filteredVehicles() { + return this.vehicles.filter(v => { + if (this.showFavorites && v.favorite != 1) return false; + if (this.searchQuery) { + const q = this.searchQuery.toLowerCase(); + if (!v.carname.toLowerCase().includes(q) && !v.plate.toLowerCase().includes(q)) return false; + } + const cls = v.vehClass || 0; + if (this.activeTab === 'cars') return ![8,13,14,15,16].includes(cls); + if (this.activeTab === 'motor') return cls === 8; + if (this.activeTab === 'bikes') return cls === 13; + if (this.activeTab === 'boat') return cls === 14; + if (this.activeTab === 'aircraft') return cls === 15 || cls === 16; + return true; + }); + } + }, + + methods: { + close() { + this.show = false; this.showAdminPanel = false; + this.selectedVehicle = null; + $.post(`https://${GetParentResourceName()}/close`, JSON.stringify({})); + }, + setTab(tab) { + this.activeTab = tab; this.showFavorites = false; + this.selectedVehicle = null; this.selectedIndex = null; + }, + toggleFavorites() { + this.showFavorites = !this.showFavorites; + this.selectedVehicle = null; this.selectedIndex = null; + }, + selectVehicle(v, index) { + this.selectedVehicle = v; this.selectedIndex = index; + $.post(`https://${GetParentResourceName()}/previewVehicle`, JSON.stringify({ modelname: v.modelname, props: v.props })); + }, + takeOut() { + if (!this.selectedVehicle) return; + $.post(`https://${GetParentResourceName()}/takeOut`, JSON.stringify({ plate: this.selectedVehicle.plate, vehClass: this.selectedVehicle.vehClass || 0 })); + this.show = false; this.showAdminPanel = false; + }, + + parkFromPanel() { + if (!this.selectedVehicle || !this.selectedVehicle.nearby) return; + $.post(`https://${GetParentResourceName()}/parkFromPanel`, JSON.stringify({ plate: this.selectedVehicle.plate })); + }, + + parkJobVehicle() { + if (!this.selectedVehicle || !this.selectedVehicle.nearby) return; + $.post(`https://${GetParentResourceName()}/parkJobVehicle`, JSON.stringify({ + plate: this.selectedVehicle.plate, + nearbyJobPlate: this.selectedVehicle.nearbyJobPlate || null, + })); + }, + + toggleFav(plate, current) { + const newVal = current == 1 ? 0 : 1; + $.post(`https://${GetParentResourceName()}/setFavorite`, JSON.stringify({ plate, value: newVal })); + const v = this.vehicles.find(x => x.plate === plate); + if (v) { v.favorite = newVal; if (this.selectedVehicle && this.selectedVehicle.plate === plate) this.selectedVehicle.favorite = newVal; } + }, + + // ADMIN + openAdmin() { + this.showAdminPanel = true; + this.isAdmin = true; + this.loadAdminGarages(); + }, + + openAdminFromGarage() { + // Via NUI Callback damit Lua die Kamera sauber aufräumt + $.post(`https://${GetParentResourceName()}/openAdminFromGarage`, JSON.stringify({})); + }, + closeAdmin() { + this.showAdminPanel = false; this.show = false; + $.post(`https://${GetParentResourceName()}/closeAdmin`, JSON.stringify({})); + }, + loadAdminGarages() { + $.post(`https://${GetParentResourceName()}/getAdminGarages`, JSON.stringify({}), (data) => { + try { + const d = typeof data === 'string' ? JSON.parse(data) : data; + if (Array.isArray(d) && d.length > 0) { + this.adminGarages = d.sort((a,b) => a.label.localeCompare(b.label)); + } + // Falls leer: Daten kommen via ADMIN_GARAGES message + } catch(e) {} + }); + }, + newGarage() { + this.editingGarage = { _isNew: true, id:'', label:'', type:'normal', access:'none', gang:'none', npc_model:'a_m_m_prolhost_01', blip_show:1, blip_show_bool:true, blip_type:357, blip_colour:3, npc_x:0,npc_y:0,npc_z:0,npc_heading:0, spawn_x:0,spawn_y:0,spawn_z:0,spawn_heading:0, park_x:0,park_y:0,park_z:0,park_heading:0 }; + this.jobVehicles = []; + }, + editGarage(g) { + this.editingGarage = Object.assign({ _isNew: false, blip_show_bool: g.blip_show == 1 }, g); + // Job-Fahrzeuge laden falls Jobgarage + if (g.type === 'jobgarage') { + this.jobVehicles = g.job_vehicles ? [...g.job_vehicles] : []; + } else { + this.jobVehicles = []; + } + }, + confirmDelete(id) { this.deleteConfirmId = id; }, + deleteGarage() { + if (!this.deleteConfirmId) return; + $.post(`https://${GetParentResourceName()}/adminDeleteGarage`, JSON.stringify({ id: this.deleteConfirmId })); + this.adminGarages = this.adminGarages.filter(g => g.id !== this.deleteConfirmId); + if (this.editingGarage && this.editingGarage.id === this.deleteConfirmId) this.editingGarage = null; + this.deleteConfirmId = null; + }, + // Job-Fahrzeug hinzufügen + addJobVehicle() { + const model = this.newJobVehicle.trim().toLowerCase(); + if (!model) return; + if (!this.jobVehicles.find(v => v.model === model)) { + this.jobVehicles.push({ model, label: model.toUpperCase() }); + } + this.newJobVehicle = ''; + }, + removeJobVehicle(index) { + this.jobVehicles.splice(index, 1); + }, + saveGarage() { + const g = this.editingGarage; + if (!g) return; + if (!g.id || !g.id.trim()) { return; } + if (!g.label || !g.label.trim()) { return; } + g.id = g.id.trim().replace(/\s+/g,'_'); + const payload = Object.assign({}, g); + delete payload._isNew; delete payload.blip_show_bool; + payload.blip_show = g.blip_show_bool ? 1 : 0; + if (g.type === 'jobgarage') { + payload.job_vehicles = this.jobVehicles; + } + $.post(`https://${GetParentResourceName()}/adminSaveGarage`, JSON.stringify(payload)); + const i = this.adminGarages.findIndex(x => x.id === payload.id); + if (i >= 0) this.adminGarages.splice(i, 1, payload); else this.adminGarages.push(payload); + this.adminGarages.sort((a,b) => a.label.localeCompare(b.label)); + this.editingGarage._isNew = false; + }, + teleportTo(g) { $.post(`https://${GetParentResourceName()}/teleportToGarage`, JSON.stringify({ x:g.npc_x, y:g.npc_y, z:g.npc_z, heading:g.npc_heading })); }, + capturePos(field) { + this.capturingField = field; + $.post(`https://${GetParentResourceName()}/startCapture`, JSON.stringify({ field })); + }, + getFieldLabel(field) { + const l = { npc:'NPC Position', spawn:'Spawn Position', park:'Einpark-Zone' }; + return l[field] || field; + }, + getTypeIcon(type) { + const m = { normal:'fas fa-car', aircraft:'fas fa-plane', boat:'fas fa-ship', jobgarage:'fas fa-briefcase', impound:'fas fa-lock', impoundboat:'fas fa-ship', impoundplane:'fas fa-plane' }; + return m[type] || 'fas fa-warehouse'; + }, + applyCapture(field, x, y, z, heading) { + if (!this.editingGarage) return; + this.editingGarage[field+'_x'] = x; + this.editingGarage[field+'_y'] = y; + this.editingGarage[field+'_z'] = z; + if (heading !== undefined) this.editingGarage[field+'_heading'] = heading; + }, + } +}); + +window.addEventListener('message', function(e) { + const msg = e.data; + if (!msg || !msg.action) return; + switch(msg.action) { + case 'OPEN': + app.show = true; app.showAdminPanel = false; + app.garageId = msg.garageId; app.garageType = msg.garageType || 'normal'; + app.vehicles = msg.vehicles || []; app.playerName = msg.playerName || ''; + app.impound = msg.impound || false; app.impoundPrice = msg.impoundPrice || 1500; + app.selectedVehicle = null; app.selectedIndex = null; + app.showFavorites = false; app.searchQuery = ''; + if (msg.garageType === 'aircraft' || msg.garageType === 'impoundplane') app.activeTab = 'aircraft'; + else if (msg.garageType === 'boat' || msg.garageType === 'impoundboat') app.activeTab = 'boat'; + else app.activeTab = 'all'; + break; + case 'CLOSE': app.show = false; app.showAdminPanel = false; break; + case 'OPEN_ADMIN': + app.show = true; app.showAdminPanel = true; app.isAdmin = true; + app.loadAdminGarages(); break; + case 'SET_PROFILE': app.playerName = msg.name || ''; app.avatar = msg.avatar || 'images/defaultimage.png'; break; + case 'SET_ADMIN': app.isAdmin = msg.isAdmin || false; break; + case 'POSITION_CAPTURED': + app.capturingField = null; + if (app.editingGarage) app.applyCapture(msg.field, msg.x, msg.y, msg.z, msg.heading); + break; + case 'CAPTURE_CANCELLED': app.capturingField = null; break; + case 'ADMIN_GARAGES': + if (msg.garages) { + app.adminGarages = msg.garages.sort((a,b) => a.label.localeCompare(b.label)); + } + break; + case 'SYNC_GARAGES_DONE': if (app.showAdminPanel) app.loadAdminGarages(); break; + } +}); + +$(document).keydown(function(e) { + if (e.keyCode === 27) { + if (app.showAdminPanel) app.closeAdmin(); + else app.close(); + } +}); diff --git a/[core]/mercyv-garage/nui/style.css b/[core]/mercyv-garage/nui/style.css new file mode 100644 index 00000000..91bb0273 --- /dev/null +++ b/[core]/mercyv-garage/nui/style.css @@ -0,0 +1,539 @@ +@font-face { + src: url('./fonts/Gilroy-ExtraBold.otf') format("OpenType"); + font-family: "GilroyBold"; +} +@font-face { + src: url('./fonts/Gilroy-Light.otf') format("OpenType"); + font-family: "GilroyLight"; +} + +:root { + --bg-base: #111213; + --bg-modal: #18191b; + --bg-sidebar: #111213; + --bg-card: #1e2022; + --bg-card-hover:#252729; + --bg-input: #1a1c1e; + --accent: #E8830A; + --accent-hover: #F5960F; + --accent-dim: rgba(232,131,10,0.12); + --accent-dim2: rgba(232,131,10,0.22); + --text-primary: #ffffff; + --text-secondary: rgba(255,255,255,0.55); + --text-muted: rgba(255,255,255,0.25); + --border: rgba(255,255,255,0.07); + --border-hover: rgba(255,255,255,0.13); + --green: #4CAF50; + --red: #EF5350; + --blue: #42A5F5; +} + +* { box-sizing: border-box; margin: 0; padding: 0; } + +html, body { + width: 100vw; height: 100vh; + overflow: hidden; + user-select: none; + -webkit-user-select: none; + font-family: "GilroyLight", "Segoe UI", sans-serif; +} + +body { background: transparent; } + +#app { width: 100vw; height: 100vh; display: flex; align-items: center; justify-content: center; } + +/* ──────────────────── BACKDROP / MODAL ──────────────────── */ + +.mv-backdrop { + position: fixed; inset: 0; + display: flex; align-items: center; justify-content: center; + background: rgba(0,0,0,0.55); + backdrop-filter: blur(2px); +} + +.mv-backdrop-transparent { + position: fixed; inset: 0; + display: flex; align-items: center; justify-content: center; + background: transparent; +} + +.mv-modal { + width: 1200px; + height: 760px; + background: var(--bg-modal); + border-radius: 14px; + overflow: hidden; + border: 1px solid rgba(255,255,255,0.12); + display: flex; + flex-direction: column; + box-shadow: 0 8px 40px rgba(0,0,0,0.7); +} + +.mv-modal-wide { width: 1300px; height: 800px; } + +/* ──────────────────── HEADER ──────────────────── */ + +.mv-header { + display: flex; align-items: center; justify-content: space-between; + padding: 12px 16px; + background: var(--bg-base); + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} + +.mv-header-left { display: flex; align-items: center; gap: 10px; } + +.mv-header-icon { + width: 34px; height: 34px; border-radius: 8px; + background: var(--accent-dim2); + border: 1px solid rgba(232,131,10,0.3); + display: flex; align-items: center; justify-content: center; + overflow: hidden; +} + +.mv-logo-img { width: 28px; height: 28px; object-fit: contain; } +.mv-logo-fallback { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: var(--accent); font-size: 14px; } + +.mv-header-title { display: flex; flex-direction: column; gap: 1px; } +.mv-title-main { font-family: "GilroyBold", sans-serif; font-size: 16px; color: var(--text-primary); letter-spacing: 0.3px; } +.mv-title-sub { font-size: 11px; color: var(--text-muted); letter-spacing: 0.4px; text-transform: uppercase; } + +.mv-header-right { display: flex; align-items: center; gap: 10px; } +.mv-player-name { font-size: 12px; color: var(--text-secondary); } + +.mv-close-btn { + width: 28px; height: 28px; border-radius: 7px; + border: 1px solid var(--border-hover); + background: rgba(255,255,255,0.04); + color: var(--text-secondary); cursor: pointer; + display: flex; align-items: center; justify-content: center; + font-size: 11px; transition: all 0.15s; +} +.mv-close-btn:hover { background: rgba(255,255,255,0.1); color: white; } + +/* ──────────────────── BODY LAYOUT ──────────────────── */ + +.mv-body { display: flex; flex: 1; overflow: hidden; } + +/* ──────────────────── SIDEBAR ──────────────────── */ + +.mv-sidebar { + width: 175px; flex-shrink: 0; + background: var(--bg-sidebar); + border-right: 1px solid var(--border); + padding: 8px 0; + display: flex; flex-direction: column; +} + +.mv-sidebar-item { + display: flex; align-items: center; gap: 10px; + padding: 11px 16px; + cursor: pointer; + border-radius: 0; + color: var(--text-secondary); + font-size: 15px; + transition: all 0.15s; + margin: 1px 8px; + border-radius: 8px; + border-left: 2px solid transparent; +} +.mv-sidebar-item i { font-size: 16px; width: 20px; text-align: center; } +.mv-sidebar-item:hover { background: rgba(255,255,255,0.04); color: rgba(255,255,255,0.75); } + +.mv-sidebar-item.active { + background: var(--accent-dim); + border-left: 2px solid var(--accent); + color: var(--accent); + font-family: "GilroyBold", sans-serif; +} +.mv-sidebar-item.active i { color: var(--accent); } + +.mv-sidebar-item.active-fav { color: #FFD700; } +.mv-sidebar-item.active-fav i { color: #FFD700; } + +.mv-sidebar-divider { height: 1px; background: var(--border); margin: 6px 14px; } +.mv-sidebar-spacer { flex: 1; } + +.mv-admin-item { color: var(--text-muted); font-size: 11px; } +.mv-admin-item:hover { color: var(--accent); } + +/* ──────────────────── MAIN AREA ──────────────────── */ + +.mv-main { flex: 1; display: grid; grid-template-rows: auto 1fr; overflow: hidden; } +.mv-main-content { display: flex; overflow: hidden; flex: 1; } + +.mv-topbar { + display: flex; align-items: center; gap: 12px; + padding: 10px 14px; + flex-shrink: 0; +} + +.mv-search-wrap { + flex: 1; position: relative; +} +.mv-search-icon { + position: absolute; left: 10px; top: 50%; transform: translateY(-50%); + color: var(--text-muted); font-size: 11px; +} +.mv-search { + width: 100%; padding: 8px 10px 8px 28px; + background: var(--bg-input); + border: 1px solid var(--border); + border-radius: 8px; + color: var(--text-secondary); + font-size: 12px; font-family: inherit; + outline: none; transition: border 0.15s; +} +.mv-search:focus { border-color: rgba(232,131,10,0.35); } +.mv-search::placeholder { color: var(--text-muted); } + +.mv-impound-badge { + font-size: 11px; color: var(--accent); + background: var(--accent-dim); + border: 1px solid rgba(232,131,10,0.2); + padding: 5px 10px; border-radius: 6px; + white-space: nowrap; +} + +/* ──────────────────── VEHICLE GRID ──────────────────── */ + +.mv-content-row { + display: flex; flex: 1; overflow: hidden; +} + +.mv-grid { + flex: 1; overflow-y: auto; + padding: 14px; + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12px; + align-content: start; +} +.mv-grid::-webkit-scrollbar { width: 0px; } + +.mv-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 10px; + padding: 10px 8px 8px; + text-align: center; + cursor: pointer; + position: relative; + transition: all 0.15s; +} +.mv-card:hover { background: var(--bg-card-hover); border-color: var(--border-hover); transform: translateY(-1px); } +.mv-card-active { border-color: var(--accent) !important; background: var(--bg-card-hover); } + +.mv-card-status { + position: absolute; top: 6px; left: 6px; + font-size: 11px; padding: 3px 7px; border-radius: 3px; + font-family: "GilroyBold", sans-serif; letter-spacing: 0.05em; +} +.status-in { background: rgba(76,175,80,0.2); color: #81C784; border: 1px solid rgba(76,175,80,0.3); } +.status-out { background: rgba(232,131,10,0.2); color: var(--accent); border: 1px solid rgba(232,131,10,0.3); } + +.mv-card-img { width: 115px; height: 70px; object-fit: contain; margin: 8px auto 8px; display: block; } +.mv-card-name { + font-size: 14px; font-family: "GilroyBold", sans-serif; + color: var(--text-primary); margin-bottom: 2px; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.mv-card-plate { font-size: 13px; color: var(--text-muted); letter-spacing: 0.08em; } +.mv-card-fav { + position: absolute; bottom: 6px; right: 7px; + font-size: 10px; color: #FFD700; opacity: 0.7; cursor: pointer; transition: opacity 0.15s; +} +.mv-card-fav:hover { opacity: 1; } + +.mv-empty { + grid-column: 1/-1; display: flex; flex-direction: column; + align-items: center; justify-content: center; + color: var(--text-muted); gap: 8px; padding: 30px; +} +.mv-empty i { font-size: 2.5rem; } +.mv-empty p { font-size: 12px; } + +/* ──────────────────── DETAIL PANEL ──────────────────── */ + +.mv-detail { + width: 300px; flex-shrink: 0; + overflow-y: auto; padding: 16px; + display: flex; flex-direction: column; gap: 8px; + border-left: 1px solid var(--border); + background: rgba(0,0,0,0.15); +} +.mv-detail::-webkit-scrollbar { width: 0px; } + +.mv-back-btn { + align-self: flex-start; + padding: 6px 12px; + background: rgba(255,255,255,0.05); + border: 1px solid var(--border); + border-radius: 7px; + color: var(--text-secondary); + font-size: 11px; cursor: pointer; + font-family: inherit; transition: background 0.15s; +} +.mv-back-btn:hover { background: rgba(255,255,255,0.1); } + +.mv-detail-img-wrap { text-align: center; padding: 8px 0; } +.mv-detail-img { width: 160px; height: 90px; object-fit: contain; } + +.mv-detail-name { font-family: "GilroyBold", sans-serif; font-size: 22px; color: white; text-align: center; } +.mv-detail-plate { font-size: 14px; color: var(--text-secondary); text-align: center; } +.mv-detail-garage{ font-size: 13px; color: var(--text-muted); text-align: center; } + +.mv-detail-status { + text-align: center; font-size: 12px; padding: 6px 12px; + border-radius: 6px; display: inline-block; align-self: center; +} +.ds-in { background: rgba(76,175,80,0.15); color: #81C784; border: 1px solid rgba(76,175,80,0.2); } +.ds-out { background: var(--accent-dim); color: var(--accent); border: 1px solid rgba(232,131,10,0.2); } + +.mv-takeout-btn { + padding: 13px; border-radius: 8px; + background: var(--accent); border: none; + color: white; font-family: "GilroyBold", sans-serif; + font-size: 14px; cursor: pointer; letter-spacing: 0.04em; + transition: background 0.15s, transform 0.1s; +} +.mv-takeout-btn:hover { background: var(--accent-hover); } +.mv-takeout-btn:active { transform: scale(0.98); } + +.mv-already-out { + text-align: center; font-size: 11px; color: var(--text-muted); + padding: 8px; background: rgba(255,255,255,0.03); + border-radius: 6px; border: 1px solid var(--border); +} +.mv-detail-fav { + text-align: center; font-size: 11px; color: #FFD700; + cursor: pointer; opacity: 0.7; transition: opacity 0.15s; +} +.mv-detail-fav:hover { opacity: 1; } + +/* ──────────────────── ADMIN SIDEBAR ──────────────────── */ + +.mv-admin-sidebar { width: 220px; } + +.mv-admin-list { flex: 1; overflow-y: auto; } +.mv-admin-list::-webkit-scrollbar { width: 3px; } +.mv-admin-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; } + +.mv-admin-list-item { + display: flex; align-items: center; gap: 8px; + padding: 9px 12px; margin: 2px 6px; border-radius: 8px; + cursor: pointer; transition: background 0.12s; + border-left: 2px solid transparent; +} +.mv-admin-list-item:hover { background: rgba(255,255,255,0.04); } +.admin-item-active { background: var(--accent-dim) !important; border-left: 2px solid var(--accent); } + +.mv-admin-item-icon { + width: 28px; height: 28px; border-radius: 6px; + display: flex; align-items: center; justify-content: center; + font-size: 11px; flex-shrink: 0; + background: rgba(255,255,255,0.06); + color: var(--text-secondary); +} +.type-icon-normal { background: rgba(76,175,80,0.15); color: #81C784; } +.type-icon-aircraft { background: rgba(66,165,245,0.15); color: #64B5F6; } +.type-icon-boat { background: rgba(0,188,212,0.15); color: #4DD0E1; } +.type-icon-jobgarage { background: rgba(232,131,10,0.15); color: var(--accent); } +.type-icon-impound, .type-icon-impoundboat, .type-icon-impoundplane + { background: rgba(239,83,80,0.15); color: #EF9A9A; } + +.mv-admin-item-info { flex: 1; min-width: 0; } +.mv-admin-item-label { font-size: 12px; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.mv-admin-item-type { font-size: 10px; color: var(--text-muted); margin-top: 1px; text-transform: uppercase; letter-spacing: 0.05em; } + +.mv-admin-item-btns { display: flex; gap: 4px; opacity: 0; transition: opacity 0.12s; } +.mv-admin-list-item:hover .mv-admin-item-btns { opacity: 1; } + +.mv-icon-btn { width: 24px; height: 24px; border-radius: 5px; border: none; cursor: pointer; font-size: 9px; display: flex; align-items: center; justify-content: center; } +.mv-icon-btn-blue { background: rgba(66,165,245,0.25); color: #64B5F6; } +.mv-icon-btn-red { background: rgba(239,83,80,0.25); color: #EF9A9A; } +.mv-icon-btn:hover { opacity: 0.85; } + +/* ──────────────────── ADMIN FORM ──────────────────── */ + +.mv-admin-main { } + +.mv-btn-orange { + padding: 7px 14px; border-radius: 7px; + background: var(--accent); border: none; + color: white; font-family: "GilroyBold", sans-serif; + font-size: 11px; cursor: pointer; letter-spacing: 0.03em; + transition: background 0.15s; white-space: nowrap; + display: flex; align-items: center; gap: 5px; +} +.mv-btn-orange:hover { background: var(--accent-hover); } + +.mv-form-scroll { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 12px; } +.mv-form-scroll::-webkit-scrollbar { width: 3px; } +.mv-form-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; } + +.mv-form-section { + background: rgba(255,255,255,0.02); + border: 1px solid var(--border); + border-radius: 9px; padding: 12px; +} +.mv-form-section-title { + font-family: "GilroyBold", sans-serif; font-size: 10px; + color: var(--text-muted); letter-spacing: 0.1em; text-transform: uppercase; + margin-bottom: 10px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; +} +.mv-form-section-title i { color: var(--accent); } + +.mv-hint-pill { + font-family: "GilroyLight", sans-serif; font-size: 10px; + color: rgba(232,131,10,0.7); background: var(--accent-dim); + padding: 2px 8px; border-radius: 10px; margin-left: 6px; + text-transform: none; letter-spacing: 0; font-weight: normal; +} + +.mv-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } + +.mv-form-field { display: flex; flex-direction: column; gap: 4px; } +.mv-form-field label { font-size: 10px; color: var(--text-muted); } +.mv-field-hint { color: rgba(255,255,255,0.2); font-size: 9px; } +.mv-form-field-check { flex-direction: row; align-items: center; gap: 8px; } + +.mv-form-field input, .mv-form-field select { + padding: 7px 10px; + background: var(--bg-input); + border: 1px solid var(--border); + border-radius: 6px; color: white; + font-family: inherit; font-size: 11px; outline: none; + transition: border 0.15s; +} +.mv-form-field input:focus, .mv-form-field select:focus { border-color: rgba(232,131,10,0.4); } +.mv-form-field select option { background: #1a1a1a; } +.mv-form-field input:disabled { opacity: 0.45; cursor: not-allowed; } + +.mv-pos-block { margin-bottom: 10px; } +.mv-pos-label { font-size: 10px; color: var(--text-secondary); margin-bottom: 5px; } +.mv-pos-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; margin-bottom: 5px; } +.mv-pos-row input { + padding: 6px 7px; background: var(--bg-input); + border: 1px solid var(--border); border-radius: 5px; + color: white; font-size: 10px; font-family: inherit; outline: none; +} +.mv-pos-row input:focus { border-color: rgba(232,131,10,0.35); } + +.mv-capture-btn { + padding: 5px 11px; border-radius: 5px; + background: rgba(232,131,10,0.12); + border: 1px solid rgba(232,131,10,0.25); + color: var(--accent); font-size: 10px; + font-family: inherit; cursor: pointer; transition: background 0.15s; + display: inline-flex; align-items: center; gap: 5px; +} +.mv-capture-btn:hover { background: rgba(232,131,10,0.2); } + +.mv-admin-empty { + flex: 1; display: flex; flex-direction: column; + align-items: center; justify-content: center; + color: var(--text-muted); gap: 10px; +} +.mv-admin-empty i { font-size: 2.5rem; } +.mv-admin-empty p { font-size: 12px; } + +/* ──────────────────── OVERLAYS ──────────────────── */ + +.mv-confirm-overlay, .mv-capture-overlay { + position: fixed; inset: 0; + background: rgba(0,0,0,0.65); + display: flex; align-items: center; justify-content: center; + z-index: 9999; +} +.mv-capture-overlay { pointer-events: none; } + +.mv-confirm-box, .mv-capture-box { + background: var(--bg-modal); + border: 1px solid var(--border); + border-radius: 12px; padding: 28px 24px; + text-align: center; max-width: 340px; width: 90%; + color: white; font-family: inherit; +} + +.mv-confirm-icon { font-size: 2rem; color: var(--red); margin-bottom: 12px; display: block; } +.mv-confirm-title { font-family: "GilroyBold", sans-serif; font-size: 15px; margin-bottom: 6px; } +.mv-confirm-sub { font-size: 12px; color: var(--text-secondary); margin-bottom: 16px; } +.mv-confirm-btns { display: flex; gap: 10px; justify-content: center; } + +.mv-btn-danger { + padding: 9px 20px; border-radius: 7px; + background: var(--red); border: none; + color: white; font-family: "GilroyBold", sans-serif; + font-size: 12px; cursor: pointer; transition: opacity 0.15s; + display: flex; align-items: center; gap: 5px; +} +.mv-btn-danger:hover { opacity: 0.85; } +.mv-btn-ghost { + padding: 9px 18px; border-radius: 7px; + background: rgba(255,255,255,0.06); + border: 1px solid var(--border-hover); + color: var(--text-secondary); + font-family: inherit; font-size: 12px; cursor: pointer; + transition: background 0.15s; +} +.mv-btn-ghost:hover { background: rgba(255,255,255,0.12); } + +.mv-capture-icon { font-size: 2.5rem; color: var(--accent); margin-bottom: 10px; display: block; } +.mv-capture-title { font-family: "GilroyBold", sans-serif; font-size: 15px; margin-bottom: 8px; } +.mv-capture-field { font-size: 12px; color: var(--accent); background: var(--accent-dim); padding: 4px 12px; border-radius: 5px; display: inline-block; margin-bottom: 10px; } +.mv-capture-hint { font-size: 12px; color: var(--text-secondary); margin-bottom: 4px; } +.mv-capture-cancel{ font-size: 11px; color: var(--text-muted); } + +kbd { + background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.15); + border-radius: 4px; padding: 1px 6px; font-size: 11px; + font-family: inherit; color: white; +} + +/* Nearby badge */ +.status-nearby { background: rgba(232,131,10,0.25); color: var(--accent); border: 1px solid rgba(232,131,10,0.4); } + +/* Einparken Button */ +.mv-parkin-btn { + padding: 13px; border-radius: 8px; + background: rgba(232,131,10,0.15); + border: 1px solid rgba(232,131,10,0.4); + color: var(--accent); + font-family: "GilroyBold", sans-serif; + font-size: 14px; cursor: pointer; letter-spacing: 0.04em; + transition: background 0.15s, transform 0.1s; + margin-top: 4px; +} +.mv-parkin-btn:hover { background: rgba(232,131,10,0.28); } +.mv-parkin-btn:active { transform: scale(0.98); } + +/* Job-Fahrzeuge Admin */ +.mv-job-vehicle-list { + display: flex; flex-direction: column; gap: 6px; + margin-bottom: 10px; +} +.mv-job-vehicle-item { + display: flex; align-items: center; justify-content: space-between; + background: rgba(255,255,255,0.04); + border: 1px solid rgba(255,255,255,0.08); + border-radius: 6px; padding: 8px 12px; +} +.mv-job-vehicle-name { + font-size: 13px; color: var(--text-secondary); +} +.mv-job-vehicle-name i { color: var(--accent); margin-right: 6px; } +.mv-job-vehicle-empty { + font-size: 12px; color: var(--text-muted); + padding: 8px; text-align: center; +} +.mv-job-vehicle-add { + display: flex; gap: 8px; align-items: center; +} +.mv-job-input { + flex: 1; padding: 8px 12px; + background: var(--bg-input); border: 1px solid var(--border); + border-radius: 8px; color: var(--text-secondary); + font-size: 13px; font-family: inherit; outline: none; +} +.mv-job-input:focus { border-color: var(--accent); } diff --git a/[core]/mercyv-garage/nui/vue.js b/[core]/mercyv-garage/nui/vue.js new file mode 100644 index 00000000..4ada213c --- /dev/null +++ b/[core]/mercyv-garage/nui/vue.js @@ -0,0 +1,11967 @@ +/*! + * Vue.js v2.6.12 + * (c) 2014-2020 Evan You + * Released under the MIT License. + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.Vue = factory()); + }(this, function () { 'use strict'; + + /* */ + + var emptyObject = Object.freeze({}); + + // These helpers produce better VM code in JS engines due to their + // explicitness and function inlining. + function isUndef (v) { + return v === undefined || v === null + } + + function isDef (v) { + return v !== undefined && v !== null + } + + function isTrue (v) { + return v === true + } + + function isFalse (v) { + return v === false + } + + /** + * Check if value is primitive. + */ + function isPrimitive (value) { + return ( + typeof value === 'string' || + typeof value === 'number' || + // $flow-disable-line + typeof value === 'symbol' || + typeof value === 'boolean' + ) + } + + /** + * Quick object check - this is primarily used to tell + * Objects from primitive values when we know the value + * is a JSON-compliant type. + */ + function isObject (obj) { + return obj !== null && typeof obj === 'object' + } + + /** + * Get the raw type string of a value, e.g., [object Object]. + */ + var _toString = Object.prototype.toString; + + function toRawType (value) { + return _toString.call(value).slice(8, -1) + } + + /** + * Strict object type check. Only returns true + * for plain JavaScript objects. + */ + function isPlainObject (obj) { + return _toString.call(obj) === '[object Object]' + } + + function isRegExp (v) { + return _toString.call(v) === '[object RegExp]' + } + + /** + * Check if val is a valid array index. + */ + function isValidArrayIndex (val) { + var n = parseFloat(String(val)); + return n >= 0 && Math.floor(n) === n && isFinite(val) + } + + function isPromise (val) { + return ( + isDef(val) && + typeof val.then === 'function' && + typeof val.catch === 'function' + ) + } + + /** + * Convert a value to a string that is actually rendered. + */ + function toString (val) { + return val == null + ? '' + : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString) + ? JSON.stringify(val, null, 2) + : String(val) + } + + /** + * Convert an input value to a number for persistence. + * If the conversion fails, return original string. + */ + function toNumber (val) { + var n = parseFloat(val); + return isNaN(n) ? val : n + } + + /** + * Make a map and return a function for checking if a key + * is in that map. + */ + function makeMap ( + str, + expectsLowerCase + ) { + var map = Object.create(null); + var list = str.split(','); + for (var i = 0; i < list.length; i++) { + map[list[i]] = true; + } + return expectsLowerCase + ? function (val) { return map[val.toLowerCase()]; } + : function (val) { return map[val]; } + } + + /** + * Check if a tag is a built-in tag. + */ + var isBuiltInTag = makeMap('slot,component', true); + + /** + * Check if an attribute is a reserved attribute. + */ + var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is'); + + /** + * Remove an item from an array. + */ + function remove (arr, item) { + if (arr.length) { + var index = arr.indexOf(item); + if (index > -1) { + return arr.splice(index, 1) + } + } + } + + /** + * Check whether an object has the property. + */ + var hasOwnProperty = Object.prototype.hasOwnProperty; + function hasOwn (obj, key) { + return hasOwnProperty.call(obj, key) + } + + /** + * Create a cached version of a pure function. + */ + function cached (fn) { + var cache = Object.create(null); + return (function cachedFn (str) { + var hit = cache[str]; + return hit || (cache[str] = fn(str)) + }) + } + + /** + * Camelize a hyphen-delimited string. + */ + var camelizeRE = /-(\w)/g; + var camelize = cached(function (str) { + return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) + }); + + /** + * Capitalize a string. + */ + var capitalize = cached(function (str) { + return str.charAt(0).toUpperCase() + str.slice(1) + }); + + /** + * Hyphenate a camelCase string. + */ + var hyphenateRE = /\B([A-Z])/g; + var hyphenate = cached(function (str) { + return str.replace(hyphenateRE, '-$1').toLowerCase() + }); + + /** + * Simple bind polyfill for environments that do not support it, + * e.g., PhantomJS 1.x. Technically, we don't need this anymore + * since native bind is now performant enough in most browsers. + * But removing it would mean breaking code that was able to run in + * PhantomJS 1.x, so this must be kept for backward compatibility. + */ + + /* istanbul ignore next */ + function polyfillBind (fn, ctx) { + function boundFn (a) { + var l = arguments.length; + return l + ? l > 1 + ? fn.apply(ctx, arguments) + : fn.call(ctx, a) + : fn.call(ctx) + } + + boundFn._length = fn.length; + return boundFn + } + + function nativeBind (fn, ctx) { + return fn.bind(ctx) + } + + var bind = Function.prototype.bind + ? nativeBind + : polyfillBind; + + /** + * Convert an Array-like object to a real Array. + */ + function toArray (list, start) { + start = start || 0; + var i = list.length - start; + var ret = new Array(i); + while (i--) { + ret[i] = list[i + start]; + } + return ret + } + + /** + * Mix properties into target object. + */ + function extend (to, _from) { + for (var key in _from) { + to[key] = _from[key]; + } + return to + } + + /** + * Merge an Array of Objects into a single Object. + */ + function toObject (arr) { + var res = {}; + for (var i = 0; i < arr.length; i++) { + if (arr[i]) { + extend(res, arr[i]); + } + } + return res + } + + /* eslint-disable no-unused-vars */ + + /** + * Perform no operation. + * Stubbing args to make Flow happy without leaving useless transpiled code + * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/). + */ + function noop (a, b, c) {} + + /** + * Always return false. + */ + var no = function (a, b, c) { return false; }; + + /* eslint-enable no-unused-vars */ + + /** + * Return the same value. + */ + var identity = function (_) { return _; }; + + /** + * Generate a string containing static keys from compiler modules. + */ + function genStaticKeys (modules) { + return modules.reduce(function (keys, m) { + return keys.concat(m.staticKeys || []) + }, []).join(',') + } + + /** + * Check if two values are loosely equal - that is, + * if they are plain objects, do they have the same shape? + */ + function looseEqual (a, b) { + if (a === b) { return true } + var isObjectA = isObject(a); + var isObjectB = isObject(b); + if (isObjectA && isObjectB) { + try { + var isArrayA = Array.isArray(a); + var isArrayB = Array.isArray(b); + if (isArrayA && isArrayB) { + return a.length === b.length && a.every(function (e, i) { + return looseEqual(e, b[i]) + }) + } else if (a instanceof Date && b instanceof Date) { + return a.getTime() === b.getTime() + } else if (!isArrayA && !isArrayB) { + var keysA = Object.keys(a); + var keysB = Object.keys(b); + return keysA.length === keysB.length && keysA.every(function (key) { + return looseEqual(a[key], b[key]) + }) + } else { + /* istanbul ignore next */ + return false + } + } catch (e) { + /* istanbul ignore next */ + return false + } + } else if (!isObjectA && !isObjectB) { + return String(a) === String(b) + } else { + return false + } + } + + /** + * Return the first index at which a loosely equal value can be + * found in the array (if value is a plain object, the array must + * contain an object of the same shape), or -1 if it is not present. + */ + function looseIndexOf (arr, val) { + for (var i = 0; i < arr.length; i++) { + if (looseEqual(arr[i], val)) { return i } + } + return -1 + } + + /** + * Ensure a function is called only once. + */ + function once (fn) { + var called = false; + return function () { + if (!called) { + called = true; + fn.apply(this, arguments); + } + } + } + + var SSR_ATTR = 'data-server-rendered'; + + var ASSET_TYPES = [ + 'component', + 'directive', + 'filter' + ]; + + var LIFECYCLE_HOOKS = [ + 'beforeCreate', + 'created', + 'beforeMount', + 'mounted', + 'beforeUpdate', + 'updated', + 'beforeDestroy', + 'destroyed', + 'activated', + 'deactivated', + 'errorCaptured', + 'serverPrefetch' + ]; + + /* */ + + + + var config = ({ + /** + * Option merge strategies (used in core/util/options) + */ + // $flow-disable-line + optionMergeStrategies: Object.create(null), + + /** + * Whether to suppress warnings. + */ + silent: false, + + /** + * Show production mode tip message on boot? + */ + productionTip: "development" !== 'production', + + /** + * Whether to enable devtools + */ + devtools: "development" !== 'production', + + /** + * Whether to record perf + */ + performance: false, + + /** + * Error handler for watcher errors + */ + errorHandler: null, + + /** + * Warn handler for watcher warns + */ + warnHandler: null, + + /** + * Ignore certain custom elements + */ + ignoredElements: [], + + /** + * Custom user key aliases for v-on + */ + // $flow-disable-line + keyCodes: Object.create(null), + + /** + * Check if a tag is reserved so that it cannot be registered as a + * component. This is platform-dependent and may be overwritten. + */ + isReservedTag: no, + + /** + * Check if an attribute is reserved so that it cannot be used as a component + * prop. This is platform-dependent and may be overwritten. + */ + isReservedAttr: no, + + /** + * Check if a tag is an unknown element. + * Platform-dependent. + */ + isUnknownElement: no, + + /** + * Get the namespace of an element + */ + getTagNamespace: noop, + + /** + * Parse the real tag name for the specific platform. + */ + parsePlatformTagName: identity, + + /** + * Check if an attribute must be bound using property, e.g. value + * Platform-dependent. + */ + mustUseProp: no, + + /** + * Perform updates asynchronously. Intended to be used by Vue Test Utils + * This will significantly reduce performance if set to false. + */ + async: true, + + /** + * Exposed for legacy reasons + */ + _lifecycleHooks: LIFECYCLE_HOOKS + }); + + /* */ + + /** + * unicode letters used for parsing html tags, component names and property paths. + * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname + * skipping \u10000-\uEFFFF due to it freezing up PhantomJS + */ + var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; + + /** + * Check if a string starts with $ or _ + */ + function isReserved (str) { + var c = (str + '').charCodeAt(0); + return c === 0x24 || c === 0x5F + } + + /** + * Define a property. + */ + function def (obj, key, val, enumerable) { + Object.defineProperty(obj, key, { + value: val, + enumerable: !!enumerable, + writable: true, + configurable: true + }); + } + + /** + * Parse simple path. + */ + var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]")); + function parsePath (path) { + if (bailRE.test(path)) { + return + } + var segments = path.split('.'); + return function (obj) { + for (var i = 0; i < segments.length; i++) { + if (!obj) { return } + obj = obj[segments[i]]; + } + return obj + } + } + + /* */ + + // can we use __proto__? + var hasProto = '__proto__' in {}; + + // Browser environment sniffing + var inBrowser = typeof window !== 'undefined'; + var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform; + var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase(); + var UA = inBrowser && window.navigator.userAgent.toLowerCase(); + var isIE = UA && /msie|trident/.test(UA); + var isIE9 = UA && UA.indexOf('msie 9.0') > 0; + var isEdge = UA && UA.indexOf('edge/') > 0; + var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android'); + var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios'); + var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge; + var isPhantomJS = UA && /phantomjs/.test(UA); + var isFF = UA && UA.match(/firefox\/(\d+)/); + + // Firefox has a "watch" function on Object.prototype... + var nativeWatch = ({}).watch; + + var supportsPassive = false; + if (inBrowser) { + try { + var opts = {}; + Object.defineProperty(opts, 'passive', ({ + get: function get () { + /* istanbul ignore next */ + supportsPassive = true; + } + })); // https://github.com/facebook/flow/issues/285 + window.addEventListener('test-passive', null, opts); + } catch (e) {} + } + + // this needs to be lazy-evaled because vue may be required before + // vue-server-renderer can set VUE_ENV + var _isServer; + var isServerRendering = function () { + if (_isServer === undefined) { + /* istanbul ignore if */ + if (!inBrowser && !inWeex && typeof global !== 'undefined') { + // detect presence of vue-server-renderer and avoid + // Webpack shimming the process + _isServer = global['process'] && global['process'].env.VUE_ENV === 'server'; + } else { + _isServer = false; + } + } + return _isServer + }; + + // detect devtools + var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; + + /* istanbul ignore next */ + function isNative (Ctor) { + return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) + } + + var hasSymbol = + typeof Symbol !== 'undefined' && isNative(Symbol) && + typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys); + + var _Set; + /* istanbul ignore if */ // $flow-disable-line + if (typeof Set !== 'undefined' && isNative(Set)) { + // use native Set when available. + _Set = Set; + } else { + // a non-standard Set polyfill that only works with primitive keys. + _Set = /*@__PURE__*/(function () { + function Set () { + this.set = Object.create(null); + } + Set.prototype.has = function has (key) { + return this.set[key] === true + }; + Set.prototype.add = function add (key) { + this.set[key] = true; + }; + Set.prototype.clear = function clear () { + this.set = Object.create(null); + }; + + return Set; + }()); + } + + /* */ + + var warn = noop; + var tip = noop; + var generateComponentTrace = (noop); // work around flow check + var formatComponentName = (noop); + + { + var hasConsole = typeof console !== 'undefined'; + var classifyRE = /(?:^|[-_])(\w)/g; + var classify = function (str) { return str + .replace(classifyRE, function (c) { return c.toUpperCase(); }) + .replace(/[-_]/g, ''); }; + + warn = function (msg, vm) { + var trace = vm ? generateComponentTrace(vm) : ''; + + if (config.warnHandler) { + config.warnHandler.call(null, msg, vm, trace); + } else if (hasConsole && (!config.silent)) { + console.error(("[Vue warn]: " + msg + trace)); + } + }; + + tip = function (msg, vm) { + if (hasConsole && (!config.silent)) { + console.warn("[Vue tip]: " + msg + ( + vm ? generateComponentTrace(vm) : '' + )); + } + }; + + formatComponentName = function (vm, includeFile) { + if (vm.$root === vm) { + return '' + } + var options = typeof vm === 'function' && vm.cid != null + ? vm.options + : vm._isVue + ? vm.$options || vm.constructor.options + : vm; + var name = options.name || options._componentTag; + var file = options.__file; + if (!name && file) { + var match = file.match(/([^/\\]+)\.vue$/); + name = match && match[1]; + } + + return ( + (name ? ("<" + (classify(name)) + ">") : "") + + (file && includeFile !== false ? (" at " + file) : '') + ) + }; + + var repeat = function (str, n) { + var res = ''; + while (n) { + if (n % 2 === 1) { res += str; } + if (n > 1) { str += str; } + n >>= 1; + } + return res + }; + + generateComponentTrace = function (vm) { + if (vm._isVue && vm.$parent) { + var tree = []; + var currentRecursiveSequence = 0; + while (vm) { + if (tree.length > 0) { + var last = tree[tree.length - 1]; + if (last.constructor === vm.constructor) { + currentRecursiveSequence++; + vm = vm.$parent; + continue + } else if (currentRecursiveSequence > 0) { + tree[tree.length - 1] = [last, currentRecursiveSequence]; + currentRecursiveSequence = 0; + } + } + tree.push(vm); + vm = vm.$parent; + } + return '\n\nfound in\n\n' + tree + .map(function (vm, i) { return ("" + (i === 0 ? '---> ' : repeat(' ', 5 + i * 2)) + (Array.isArray(vm) + ? ((formatComponentName(vm[0])) + "... (" + (vm[1]) + " recursive calls)") + : formatComponentName(vm))); }) + .join('\n') + } else { + return ("\n\n(found in " + (formatComponentName(vm)) + ")") + } + }; + } + + /* */ + + var uid = 0; + + /** + * A dep is an observable that can have multiple + * directives subscribing to it. + */ + var Dep = function Dep () { + this.id = uid++; + this.subs = []; + }; + + Dep.prototype.addSub = function addSub (sub) { + this.subs.push(sub); + }; + + Dep.prototype.removeSub = function removeSub (sub) { + remove(this.subs, sub); + }; + + Dep.prototype.depend = function depend () { + if (Dep.target) { + Dep.target.addDep(this); + } + }; + + Dep.prototype.notify = function notify () { + // stabilize the subscriber list first + var subs = this.subs.slice(); + if (!config.async) { + // subs aren't sorted in scheduler if not running async + // we need to sort them now to make sure they fire in correct + // order + subs.sort(function (a, b) { return a.id - b.id; }); + } + for (var i = 0, l = subs.length; i < l; i++) { + subs[i].update(); + } + }; + + // The current target watcher being evaluated. + // This is globally unique because only one watcher + // can be evaluated at a time. + Dep.target = null; + var targetStack = []; + + function pushTarget (target) { + targetStack.push(target); + Dep.target = target; + } + + function popTarget () { + targetStack.pop(); + Dep.target = targetStack[targetStack.length - 1]; + } + + /* */ + + var VNode = function VNode ( + tag, + data, + children, + text, + elm, + context, + componentOptions, + asyncFactory + ) { + this.tag = tag; + this.data = data; + this.children = children; + this.text = text; + this.elm = elm; + this.ns = undefined; + this.context = context; + this.fnContext = undefined; + this.fnOptions = undefined; + this.fnScopeId = undefined; + this.key = data && data.key; + this.componentOptions = componentOptions; + this.componentInstance = undefined; + this.parent = undefined; + this.raw = false; + this.isStatic = false; + this.isRootInsert = true; + this.isComment = false; + this.isCloned = false; + this.isOnce = false; + this.asyncFactory = asyncFactory; + this.asyncMeta = undefined; + this.isAsyncPlaceholder = false; + }; + + var prototypeAccessors = { child: { configurable: true } }; + + // DEPRECATED: alias for componentInstance for backwards compat. + /* istanbul ignore next */ + prototypeAccessors.child.get = function () { + return this.componentInstance + }; + + Object.defineProperties( VNode.prototype, prototypeAccessors ); + + var createEmptyVNode = function (text) { + if ( text === void 0 ) text = ''; + + var node = new VNode(); + node.text = text; + node.isComment = true; + return node + }; + + function createTextVNode (val) { + return new VNode(undefined, undefined, undefined, String(val)) + } + + // optimized shallow clone + // used for static nodes and slot nodes because they may be reused across + // multiple renders, cloning them avoids errors when DOM manipulations rely + // on their elm reference. + function cloneVNode (vnode) { + var cloned = new VNode( + vnode.tag, + vnode.data, + // #7975 + // clone children array to avoid mutating original in case of cloning + // a child. + vnode.children && vnode.children.slice(), + vnode.text, + vnode.elm, + vnode.context, + vnode.componentOptions, + vnode.asyncFactory + ); + cloned.ns = vnode.ns; + cloned.isStatic = vnode.isStatic; + cloned.key = vnode.key; + cloned.isComment = vnode.isComment; + cloned.fnContext = vnode.fnContext; + cloned.fnOptions = vnode.fnOptions; + cloned.fnScopeId = vnode.fnScopeId; + cloned.asyncMeta = vnode.asyncMeta; + cloned.isCloned = true; + return cloned + } + + /* + * not type checking this file because flow doesn't play well with + * dynamically accessing methods on Array prototype + */ + + var arrayProto = Array.prototype; + var arrayMethods = Object.create(arrayProto); + + var methodsToPatch = [ + 'push', + 'pop', + 'shift', + 'unshift', + 'splice', + 'sort', + 'reverse' + ]; + + /** + * Intercept mutating methods and emit events + */ + methodsToPatch.forEach(function (method) { + // cache original method + var original = arrayProto[method]; + def(arrayMethods, method, function mutator () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var result = original.apply(this, args); + var ob = this.__ob__; + var inserted; + switch (method) { + case 'push': + case 'unshift': + inserted = args; + break + case 'splice': + inserted = args.slice(2); + break + } + if (inserted) { ob.observeArray(inserted); } + // notify change + ob.dep.notify(); + return result + }); + }); + + /* */ + + var arrayKeys = Object.getOwnPropertyNames(arrayMethods); + + /** + * In some cases we may want to disable observation inside a component's + * update computation. + */ + var shouldObserve = true; + + function toggleObserving (value) { + shouldObserve = value; + } + + /** + * Observer class that is attached to each observed + * object. Once attached, the observer converts the target + * object's property keys into getter/setters that + * collect dependencies and dispatch updates. + */ + var Observer = function Observer (value) { + this.value = value; + this.dep = new Dep(); + this.vmCount = 0; + def(value, '__ob__', this); + if (Array.isArray(value)) { + if (hasProto) { + protoAugment(value, arrayMethods); + } else { + copyAugment(value, arrayMethods, arrayKeys); + } + this.observeArray(value); + } else { + this.walk(value); + } + }; + + /** + * Walk through all properties and convert them into + * getter/setters. This method should only be called when + * value type is Object. + */ + Observer.prototype.walk = function walk (obj) { + var keys = Object.keys(obj); + for (var i = 0; i < keys.length; i++) { + defineReactive$$1(obj, keys[i]); + } + }; + + /** + * Observe a list of Array items. + */ + Observer.prototype.observeArray = function observeArray (items) { + for (var i = 0, l = items.length; i < l; i++) { + observe(items[i]); + } + }; + + // helpers + + /** + * Augment a target Object or Array by intercepting + * the prototype chain using __proto__ + */ + function protoAugment (target, src) { + /* eslint-disable no-proto */ + target.__proto__ = src; + /* eslint-enable no-proto */ + } + + /** + * Augment a target Object or Array by defining + * hidden properties. + */ + /* istanbul ignore next */ + function copyAugment (target, src, keys) { + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + def(target, key, src[key]); + } + } + + /** + * Attempt to create an observer instance for a value, + * returns the new observer if successfully observed, + * or the existing observer if the value already has one. + */ + function observe (value, asRootData) { + if (!isObject(value) || value instanceof VNode) { + return + } + var ob; + if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { + ob = value.__ob__; + } else if ( + shouldObserve && + !isServerRendering() && + (Array.isArray(value) || isPlainObject(value)) && + Object.isExtensible(value) && + !value._isVue + ) { + ob = new Observer(value); + } + if (asRootData && ob) { + ob.vmCount++; + } + return ob + } + + /** + * Define a reactive property on an Object. + */ + function defineReactive$$1 ( + obj, + key, + val, + customSetter, + shallow + ) { + var dep = new Dep(); + + var property = Object.getOwnPropertyDescriptor(obj, key); + if (property && property.configurable === false) { + return + } + + // cater for pre-defined getter/setters + var getter = property && property.get; + var setter = property && property.set; + if ((!getter || setter) && arguments.length === 2) { + val = obj[key]; + } + + var childOb = !shallow && observe(val); + Object.defineProperty(obj, key, { + enumerable: true, + configurable: true, + get: function reactiveGetter () { + var value = getter ? getter.call(obj) : val; + if (Dep.target) { + dep.depend(); + if (childOb) { + childOb.dep.depend(); + if (Array.isArray(value)) { + dependArray(value); + } + } + } + return value + }, + set: function reactiveSetter (newVal) { + var value = getter ? getter.call(obj) : val; + /* eslint-disable no-self-compare */ + if (newVal === value || (newVal !== newVal && value !== value)) { + return + } + /* eslint-enable no-self-compare */ + if (customSetter) { + customSetter(); + } + // #7981: for accessor properties without setter + if (getter && !setter) { return } + if (setter) { + setter.call(obj, newVal); + } else { + val = newVal; + } + childOb = !shallow && observe(newVal); + dep.notify(); + } + }); + } + + /** + * Set a property on an object. Adds the new property and + * triggers change notification if the property doesn't + * already exist. + */ + function set (target, key, val) { + if (isUndef(target) || isPrimitive(target) + ) { + warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target)))); + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + target.length = Math.max(target.length, key); + target.splice(key, 1, val); + return val + } + if (key in target && !(key in Object.prototype)) { + target[key] = val; + return val + } + var ob = (target).__ob__; + if (target._isVue || (ob && ob.vmCount)) { + warn( + 'Avoid adding reactive properties to a Vue instance or its root $data ' + + 'at runtime - declare it upfront in the data option.' + ); + return val + } + if (!ob) { + target[key] = val; + return val + } + defineReactive$$1(ob.value, key, val); + ob.dep.notify(); + return val + } + + /** + * Delete a property and trigger change if necessary. + */ + function del (target, key) { + if (isUndef(target) || isPrimitive(target) + ) { + warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target)))); + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + target.splice(key, 1); + return + } + var ob = (target).__ob__; + if (target._isVue || (ob && ob.vmCount)) { + warn( + 'Avoid deleting properties on a Vue instance or its root $data ' + + '- just set it to null.' + ); + return + } + if (!hasOwn(target, key)) { + return + } + delete target[key]; + if (!ob) { + return + } + ob.dep.notify(); + } + + /** + * Collect dependencies on array elements when the array is touched, since + * we cannot intercept array element access like property getters. + */ + function dependArray (value) { + for (var e = (void 0), i = 0, l = value.length; i < l; i++) { + e = value[i]; + e && e.__ob__ && e.__ob__.dep.depend(); + if (Array.isArray(e)) { + dependArray(e); + } + } + } + + /* */ + + /** + * Option overwriting strategies are functions that handle + * how to merge a parent option value and a child option + * value into the final value. + */ + var strats = config.optionMergeStrategies; + + /** + * Options with restrictions + */ + { + strats.el = strats.propsData = function (parent, child, vm, key) { + if (!vm) { + warn( + "option \"" + key + "\" can only be used during instance " + + 'creation with the `new` keyword.' + ); + } + return defaultStrat(parent, child) + }; + } + + /** + * Helper that recursively merges two data objects together. + */ + function mergeData (to, from) { + if (!from) { return to } + var key, toVal, fromVal; + + var keys = hasSymbol + ? Reflect.ownKeys(from) + : Object.keys(from); + + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + // in case the object is already observed... + if (key === '__ob__') { continue } + toVal = to[key]; + fromVal = from[key]; + if (!hasOwn(to, key)) { + set(to, key, fromVal); + } else if ( + toVal !== fromVal && + isPlainObject(toVal) && + isPlainObject(fromVal) + ) { + mergeData(toVal, fromVal); + } + } + return to + } + + /** + * Data + */ + function mergeDataOrFn ( + parentVal, + childVal, + vm + ) { + if (!vm) { + // in a Vue.extend merge, both should be functions + if (!childVal) { + return parentVal + } + if (!parentVal) { + return childVal + } + // when parentVal & childVal are both present, + // we need to return a function that returns the + // merged result of both functions... no need to + // check if parentVal is a function here because + // it has to be a function to pass previous merges. + return function mergedDataFn () { + return mergeData( + typeof childVal === 'function' ? childVal.call(this, this) : childVal, + typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal + ) + } + } else { + return function mergedInstanceDataFn () { + // instance merge + var instanceData = typeof childVal === 'function' + ? childVal.call(vm, vm) + : childVal; + var defaultData = typeof parentVal === 'function' + ? parentVal.call(vm, vm) + : parentVal; + if (instanceData) { + return mergeData(instanceData, defaultData) + } else { + return defaultData + } + } + } + } + + strats.data = function ( + parentVal, + childVal, + vm + ) { + if (!vm) { + if (childVal && typeof childVal !== 'function') { + warn( + 'The "data" option should be a function ' + + 'that returns a per-instance value in component ' + + 'definitions.', + vm + ); + + return parentVal + } + return mergeDataOrFn(parentVal, childVal) + } + + return mergeDataOrFn(parentVal, childVal, vm) + }; + + /** + * Hooks and props are merged as arrays. + */ + function mergeHook ( + parentVal, + childVal + ) { + var res = childVal + ? parentVal + ? parentVal.concat(childVal) + : Array.isArray(childVal) + ? childVal + : [childVal] + : parentVal; + return res + ? dedupeHooks(res) + : res + } + + function dedupeHooks (hooks) { + var res = []; + for (var i = 0; i < hooks.length; i++) { + if (res.indexOf(hooks[i]) === -1) { + res.push(hooks[i]); + } + } + return res + } + + LIFECYCLE_HOOKS.forEach(function (hook) { + strats[hook] = mergeHook; + }); + + /** + * Assets + * + * When a vm is present (instance creation), we need to do + * a three-way merge between constructor options, instance + * options and parent options. + */ + function mergeAssets ( + parentVal, + childVal, + vm, + key + ) { + var res = Object.create(parentVal || null); + if (childVal) { + assertObjectType(key, childVal, vm); + return extend(res, childVal) + } else { + return res + } + } + + ASSET_TYPES.forEach(function (type) { + strats[type + 's'] = mergeAssets; + }); + + /** + * Watchers. + * + * Watchers hashes should not overwrite one + * another, so we merge them as arrays. + */ + strats.watch = function ( + parentVal, + childVal, + vm, + key + ) { + // work around Firefox's Object.prototype.watch... + if (parentVal === nativeWatch) { parentVal = undefined; } + if (childVal === nativeWatch) { childVal = undefined; } + /* istanbul ignore if */ + if (!childVal) { return Object.create(parentVal || null) } + { + assertObjectType(key, childVal, vm); + } + if (!parentVal) { return childVal } + var ret = {}; + extend(ret, parentVal); + for (var key$1 in childVal) { + var parent = ret[key$1]; + var child = childVal[key$1]; + if (parent && !Array.isArray(parent)) { + parent = [parent]; + } + ret[key$1] = parent + ? parent.concat(child) + : Array.isArray(child) ? child : [child]; + } + return ret + }; + + /** + * Other object hashes. + */ + strats.props = + strats.methods = + strats.inject = + strats.computed = function ( + parentVal, + childVal, + vm, + key + ) { + if (childVal && "development" !== 'production') { + assertObjectType(key, childVal, vm); + } + if (!parentVal) { return childVal } + var ret = Object.create(null); + extend(ret, parentVal); + if (childVal) { extend(ret, childVal); } + return ret + }; + strats.provide = mergeDataOrFn; + + /** + * Default strategy. + */ + var defaultStrat = function (parentVal, childVal) { + return childVal === undefined + ? parentVal + : childVal + }; + + /** + * Validate component names + */ + function checkComponents (options) { + for (var key in options.components) { + validateComponentName(key); + } + } + + function validateComponentName (name) { + if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "]*$")).test(name)) { + warn( + 'Invalid component name: "' + name + '". Component names ' + + 'should conform to valid custom element name in html5 specification.' + ); + } + if (isBuiltInTag(name) || config.isReservedTag(name)) { + warn( + 'Do not use built-in or reserved HTML elements as component ' + + 'id: ' + name + ); + } + } + + /** + * Ensure all props option syntax are normalized into the + * Object-based format. + */ + function normalizeProps (options, vm) { + var props = options.props; + if (!props) { return } + var res = {}; + var i, val, name; + if (Array.isArray(props)) { + i = props.length; + while (i--) { + val = props[i]; + if (typeof val === 'string') { + name = camelize(val); + res[name] = { type: null }; + } else { + warn('props must be strings when using array syntax.'); + } + } + } else if (isPlainObject(props)) { + for (var key in props) { + val = props[key]; + name = camelize(key); + res[name] = isPlainObject(val) + ? val + : { type: val }; + } + } else { + warn( + "Invalid value for option \"props\": expected an Array or an Object, " + + "but got " + (toRawType(props)) + ".", + vm + ); + } + options.props = res; + } + + /** + * Normalize all injections into Object-based format + */ + function normalizeInject (options, vm) { + var inject = options.inject; + if (!inject) { return } + var normalized = options.inject = {}; + if (Array.isArray(inject)) { + for (var i = 0; i < inject.length; i++) { + normalized[inject[i]] = { from: inject[i] }; + } + } else if (isPlainObject(inject)) { + for (var key in inject) { + var val = inject[key]; + normalized[key] = isPlainObject(val) + ? extend({ from: key }, val) + : { from: val }; + } + } else { + warn( + "Invalid value for option \"inject\": expected an Array or an Object, " + + "but got " + (toRawType(inject)) + ".", + vm + ); + } + } + + /** + * Normalize raw function directives into object format. + */ + function normalizeDirectives (options) { + var dirs = options.directives; + if (dirs) { + for (var key in dirs) { + var def$$1 = dirs[key]; + if (typeof def$$1 === 'function') { + dirs[key] = { bind: def$$1, update: def$$1 }; + } + } + } + } + + function assertObjectType (name, value, vm) { + if (!isPlainObject(value)) { + warn( + "Invalid value for option \"" + name + "\": expected an Object, " + + "but got " + (toRawType(value)) + ".", + vm + ); + } + } + + /** + * Merge two option objects into a new one. + * Core utility used in both instantiation and inheritance. + */ + function mergeOptions ( + parent, + child, + vm + ) { + { + checkComponents(child); + } + + if (typeof child === 'function') { + child = child.options; + } + + normalizeProps(child, vm); + normalizeInject(child, vm); + normalizeDirectives(child); + + // Apply extends and mixins on the child options, + // but only if it is a raw options object that isn't + // the result of another mergeOptions call. + // Only merged options has the _base property. + if (!child._base) { + if (child.extends) { + parent = mergeOptions(parent, child.extends, vm); + } + if (child.mixins) { + for (var i = 0, l = child.mixins.length; i < l; i++) { + parent = mergeOptions(parent, child.mixins[i], vm); + } + } + } + + var options = {}; + var key; + for (key in parent) { + mergeField(key); + } + for (key in child) { + if (!hasOwn(parent, key)) { + mergeField(key); + } + } + function mergeField (key) { + var strat = strats[key] || defaultStrat; + options[key] = strat(parent[key], child[key], vm, key); + } + return options + } + + /** + * Resolve an asset. + * This function is used because child instances need access + * to assets defined in its ancestor chain. + */ + function resolveAsset ( + options, + type, + id, + warnMissing + ) { + /* istanbul ignore if */ + if (typeof id !== 'string') { + return + } + var assets = options[type]; + // check local registration variations first + if (hasOwn(assets, id)) { return assets[id] } + var camelizedId = camelize(id); + if (hasOwn(assets, camelizedId)) { return assets[camelizedId] } + var PascalCaseId = capitalize(camelizedId); + if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] } + // fallback to prototype chain + var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; + if (warnMissing && !res) { + warn( + 'Failed to resolve ' + type.slice(0, -1) + ': ' + id, + options + ); + } + return res + } + + /* */ + + + + function validateProp ( + key, + propOptions, + propsData, + vm + ) { + var prop = propOptions[key]; + var absent = !hasOwn(propsData, key); + var value = propsData[key]; + // boolean casting + var booleanIndex = getTypeIndex(Boolean, prop.type); + if (booleanIndex > -1) { + if (absent && !hasOwn(prop, 'default')) { + value = false; + } else if (value === '' || value === hyphenate(key)) { + // only cast empty string / same name to boolean if + // boolean has higher priority + var stringIndex = getTypeIndex(String, prop.type); + if (stringIndex < 0 || booleanIndex < stringIndex) { + value = true; + } + } + } + // check default value + if (value === undefined) { + value = getPropDefaultValue(vm, prop, key); + // since the default value is a fresh copy, + // make sure to observe it. + var prevShouldObserve = shouldObserve; + toggleObserving(true); + observe(value); + toggleObserving(prevShouldObserve); + } + { + assertProp(prop, key, value, vm, absent); + } + return value + } + + /** + * Get the default value of a prop. + */ + function getPropDefaultValue (vm, prop, key) { + // no default, return undefined + if (!hasOwn(prop, 'default')) { + return undefined + } + var def = prop.default; + // warn against non-factory defaults for Object & Array + if (isObject(def)) { + warn( + 'Invalid default value for prop "' + key + '": ' + + 'Props with type Object/Array must use a factory function ' + + 'to return the default value.', + vm + ); + } + // the raw prop value was also undefined from previous render, + // return previous default value to avoid unnecessary watcher trigger + if (vm && vm.$options.propsData && + vm.$options.propsData[key] === undefined && + vm._props[key] !== undefined + ) { + return vm._props[key] + } + // call factory function for non-Function types + // a value is Function if its prototype is function even across different execution context + return typeof def === 'function' && getType(prop.type) !== 'Function' + ? def.call(vm) + : def + } + + /** + * Assert whether a prop is valid. + */ + function assertProp ( + prop, + name, + value, + vm, + absent + ) { + if (prop.required && absent) { + warn( + 'Missing required prop: "' + name + '"', + vm + ); + return + } + if (value == null && !prop.required) { + return + } + var type = prop.type; + var valid = !type || type === true; + var expectedTypes = []; + if (type) { + if (!Array.isArray(type)) { + type = [type]; + } + for (var i = 0; i < type.length && !valid; i++) { + var assertedType = assertType(value, type[i]); + expectedTypes.push(assertedType.expectedType || ''); + valid = assertedType.valid; + } + } + + if (!valid) { + warn( + getInvalidTypeMessage(name, value, expectedTypes), + vm + ); + return + } + var validator = prop.validator; + if (validator) { + if (!validator(value)) { + warn( + 'Invalid prop: custom validator check failed for prop "' + name + '".', + vm + ); + } + } + } + + var simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/; + + function assertType (value, type) { + var valid; + var expectedType = getType(type); + if (simpleCheckRE.test(expectedType)) { + var t = typeof value; + valid = t === expectedType.toLowerCase(); + // for primitive wrapper objects + if (!valid && t === 'object') { + valid = value instanceof type; + } + } else if (expectedType === 'Object') { + valid = isPlainObject(value); + } else if (expectedType === 'Array') { + valid = Array.isArray(value); + } else { + valid = value instanceof type; + } + return { + valid: valid, + expectedType: expectedType + } + } + + /** + * Use function string name to check built-in types, + * because a simple equality check will fail when running + * across different vms / iframes. + */ + function getType (fn) { + var match = fn && fn.toString().match(/^\s*function (\w+)/); + return match ? match[1] : '' + } + + function isSameType (a, b) { + return getType(a) === getType(b) + } + + function getTypeIndex (type, expectedTypes) { + if (!Array.isArray(expectedTypes)) { + return isSameType(expectedTypes, type) ? 0 : -1 + } + for (var i = 0, len = expectedTypes.length; i < len; i++) { + if (isSameType(expectedTypes[i], type)) { + return i + } + } + return -1 + } + + function getInvalidTypeMessage (name, value, expectedTypes) { + var message = "Invalid prop: type check failed for prop \"" + name + "\"." + + " Expected " + (expectedTypes.map(capitalize).join(', ')); + var expectedType = expectedTypes[0]; + var receivedType = toRawType(value); + var expectedValue = styleValue(value, expectedType); + var receivedValue = styleValue(value, receivedType); + // check if we need to specify expected value + if (expectedTypes.length === 1 && + isExplicable(expectedType) && + !isBoolean(expectedType, receivedType)) { + message += " with value " + expectedValue; + } + message += ", got " + receivedType + " "; + // check if we need to specify received value + if (isExplicable(receivedType)) { + message += "with value " + receivedValue + "."; + } + return message + } + + function styleValue (value, type) { + if (type === 'String') { + return ("\"" + value + "\"") + } else if (type === 'Number') { + return ("" + (Number(value))) + } else { + return ("" + value) + } + } + + function isExplicable (value) { + var explicitTypes = ['string', 'number', 'boolean']; + return explicitTypes.some(function (elem) { return value.toLowerCase() === elem; }) + } + + function isBoolean () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return args.some(function (elem) { return elem.toLowerCase() === 'boolean'; }) + } + + /* */ + + function handleError (err, vm, info) { + // Deactivate deps tracking while processing error handler to avoid possible infinite rendering. + // See: https://github.com/vuejs/vuex/issues/1505 + pushTarget(); + try { + if (vm) { + var cur = vm; + while ((cur = cur.$parent)) { + var hooks = cur.$options.errorCaptured; + if (hooks) { + for (var i = 0; i < hooks.length; i++) { + try { + var capture = hooks[i].call(cur, err, vm, info) === false; + if (capture) { return } + } catch (e) { + globalHandleError(e, cur, 'errorCaptured hook'); + } + } + } + } + } + globalHandleError(err, vm, info); + } finally { + popTarget(); + } + } + + function invokeWithErrorHandling ( + handler, + context, + args, + vm, + info + ) { + var res; + try { + res = args ? handler.apply(context, args) : handler.call(context); + if (res && !res._isVue && isPromise(res) && !res._handled) { + res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); }); + // issue #9511 + // avoid catch triggering multiple times when nested calls + res._handled = true; + } + } catch (e) { + handleError(e, vm, info); + } + return res + } + + function globalHandleError (err, vm, info) { + if (config.errorHandler) { + try { + return config.errorHandler.call(null, err, vm, info) + } catch (e) { + // if the user intentionally throws the original error in the handler, + // do not log it twice + if (e !== err) { + logError(e, null, 'config.errorHandler'); + } + } + } + logError(err, vm, info); + } + + function logError (err, vm, info) { + { + warn(("Error in " + info + ": \"" + (err.toString()) + "\""), vm); + } + /* istanbul ignore else */ + if ((inBrowser || inWeex) && typeof console !== 'undefined') { + console.error(err); + } else { + throw err + } + } + + /* */ + + var isUsingMicroTask = false; + + var callbacks = []; + var pending = false; + + function flushCallbacks () { + pending = false; + var copies = callbacks.slice(0); + callbacks.length = 0; + for (var i = 0; i < copies.length; i++) { + copies[i](); + } + } + + // Here we have async deferring wrappers using microtasks. + // In 2.5 we used (macro) tasks (in combination with microtasks). + // However, it has subtle problems when state is changed right before repaint + // (e.g. #6813, out-in transitions). + // Also, using (macro) tasks in event handler would cause some weird behaviors + // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109). + // So we now use microtasks everywhere, again. + // A major drawback of this tradeoff is that there are some scenarios + // where microtasks have too high a priority and fire in between supposedly + // sequential events (e.g. #4521, #6690, which have workarounds) + // or even between bubbling of the same event (#6566). + var timerFunc; + + // The nextTick behavior leverages the microtask queue, which can be accessed + // via either native Promise.then or MutationObserver. + // MutationObserver has wider support, however it is seriously bugged in + // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It + // completely stops working after triggering a few times... so, if native + // Promise is available, we will use it: + /* istanbul ignore next, $flow-disable-line */ + if (typeof Promise !== 'undefined' && isNative(Promise)) { + var p = Promise.resolve(); + timerFunc = function () { + p.then(flushCallbacks); + // In problematic UIWebViews, Promise.then doesn't completely break, but + // it can get stuck in a weird state where callbacks are pushed into the + // microtask queue but the queue isn't being flushed, until the browser + // needs to do some other work, e.g. handle a timer. Therefore we can + // "force" the microtask queue to be flushed by adding an empty timer. + if (isIOS) { setTimeout(noop); } + }; + isUsingMicroTask = true; + } else if (!isIE && typeof MutationObserver !== 'undefined' && ( + isNative(MutationObserver) || + // PhantomJS and iOS 7.x + MutationObserver.toString() === '[object MutationObserverConstructor]' + )) { + // Use MutationObserver where native Promise is not available, + // e.g. PhantomJS, iOS7, Android 4.4 + // (#6466 MutationObserver is unreliable in IE11) + var counter = 1; + var observer = new MutationObserver(flushCallbacks); + var textNode = document.createTextNode(String(counter)); + observer.observe(textNode, { + characterData: true + }); + timerFunc = function () { + counter = (counter + 1) % 2; + textNode.data = String(counter); + }; + isUsingMicroTask = true; + } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { + // Fallback to setImmediate. + // Technically it leverages the (macro) task queue, + // but it is still a better choice than setTimeout. + timerFunc = function () { + setImmediate(flushCallbacks); + }; + } else { + // Fallback to setTimeout. + timerFunc = function () { + setTimeout(flushCallbacks, 0); + }; + } + + function nextTick (cb, ctx) { + var _resolve; + callbacks.push(function () { + if (cb) { + try { + cb.call(ctx); + } catch (e) { + handleError(e, ctx, 'nextTick'); + } + } else if (_resolve) { + _resolve(ctx); + } + }); + if (!pending) { + pending = true; + timerFunc(); + } + // $flow-disable-line + if (!cb && typeof Promise !== 'undefined') { + return new Promise(function (resolve) { + _resolve = resolve; + }) + } + } + + /* */ + + var mark; + var measure; + + { + var perf = inBrowser && window.performance; + /* istanbul ignore if */ + if ( + perf && + perf.mark && + perf.measure && + perf.clearMarks && + perf.clearMeasures + ) { + mark = function (tag) { return perf.mark(tag); }; + measure = function (name, startTag, endTag) { + perf.measure(name, startTag, endTag); + perf.clearMarks(startTag); + perf.clearMarks(endTag); + // perf.clearMeasures(name) + }; + } + } + + /* not type checking this file because flow doesn't play well with Proxy */ + + var initProxy; + + { + var allowedGlobals = makeMap( + 'Infinity,undefined,NaN,isFinite,isNaN,' + + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + + 'require' // for Webpack/Browserify + ); + + var warnNonPresent = function (target, key) { + warn( + "Property or method \"" + key + "\" is not defined on the instance but " + + 'referenced during render. Make sure that this property is reactive, ' + + 'either in the data option, or for class-based components, by ' + + 'initializing the property. ' + + 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.', + target + ); + }; + + var warnReservedPrefix = function (target, key) { + warn( + "Property \"" + key + "\" must be accessed with \"$data." + key + "\" because " + + 'properties starting with "$" or "_" are not proxied in the Vue instance to ' + + 'prevent conflicts with Vue internals. ' + + 'See: https://vuejs.org/v2/api/#data', + target + ); + }; + + var hasProxy = + typeof Proxy !== 'undefined' && isNative(Proxy); + + if (hasProxy) { + var isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact'); + config.keyCodes = new Proxy(config.keyCodes, { + set: function set (target, key, value) { + if (isBuiltInModifier(key)) { + warn(("Avoid overwriting built-in modifier in config.keyCodes: ." + key)); + return false + } else { + target[key] = value; + return true + } + } + }); + } + + var hasHandler = { + has: function has (target, key) { + var has = key in target; + var isAllowed = allowedGlobals(key) || + (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data)); + if (!has && !isAllowed) { + if (key in target.$data) { warnReservedPrefix(target, key); } + else { warnNonPresent(target, key); } + } + return has || !isAllowed + } + }; + + var getHandler = { + get: function get (target, key) { + if (typeof key === 'string' && !(key in target)) { + if (key in target.$data) { warnReservedPrefix(target, key); } + else { warnNonPresent(target, key); } + } + return target[key] + } + }; + + initProxy = function initProxy (vm) { + if (hasProxy) { + // determine which proxy handler to use + var options = vm.$options; + var handlers = options.render && options.render._withStripped + ? getHandler + : hasHandler; + vm._renderProxy = new Proxy(vm, handlers); + } else { + vm._renderProxy = vm; + } + }; + } + + /* */ + + var seenObjects = new _Set(); + + /** + * Recursively traverse an object to evoke all converted + * getters, so that every nested property inside the object + * is collected as a "deep" dependency. + */ + function traverse (val) { + _traverse(val, seenObjects); + seenObjects.clear(); + } + + function _traverse (val, seen) { + var i, keys; + var isA = Array.isArray(val); + if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { + return + } + if (val.__ob__) { + var depId = val.__ob__.dep.id; + if (seen.has(depId)) { + return + } + seen.add(depId); + } + if (isA) { + i = val.length; + while (i--) { _traverse(val[i], seen); } + } else { + keys = Object.keys(val); + i = keys.length; + while (i--) { _traverse(val[keys[i]], seen); } + } + } + + /* */ + + var normalizeEvent = cached(function (name) { + var passive = name.charAt(0) === '&'; + name = passive ? name.slice(1) : name; + var once$$1 = name.charAt(0) === '~'; // Prefixed last, checked first + name = once$$1 ? name.slice(1) : name; + var capture = name.charAt(0) === '!'; + name = capture ? name.slice(1) : name; + return { + name: name, + once: once$$1, + capture: capture, + passive: passive + } + }); + + function createFnInvoker (fns, vm) { + function invoker () { + var arguments$1 = arguments; + + var fns = invoker.fns; + if (Array.isArray(fns)) { + var cloned = fns.slice(); + for (var i = 0; i < cloned.length; i++) { + invokeWithErrorHandling(cloned[i], null, arguments$1, vm, "v-on handler"); + } + } else { + // return handler return value for single handlers + return invokeWithErrorHandling(fns, null, arguments, vm, "v-on handler") + } + } + invoker.fns = fns; + return invoker + } + + function updateListeners ( + on, + oldOn, + add, + remove$$1, + createOnceHandler, + vm + ) { + var name, def$$1, cur, old, event; + for (name in on) { + def$$1 = cur = on[name]; + old = oldOn[name]; + event = normalizeEvent(name); + if (isUndef(cur)) { + warn( + "Invalid handler for event \"" + (event.name) + "\": got " + String(cur), + vm + ); + } else if (isUndef(old)) { + if (isUndef(cur.fns)) { + cur = on[name] = createFnInvoker(cur, vm); + } + if (isTrue(event.once)) { + cur = on[name] = createOnceHandler(event.name, cur, event.capture); + } + add(event.name, cur, event.capture, event.passive, event.params); + } else if (cur !== old) { + old.fns = cur; + on[name] = old; + } + } + for (name in oldOn) { + if (isUndef(on[name])) { + event = normalizeEvent(name); + remove$$1(event.name, oldOn[name], event.capture); + } + } + } + + /* */ + + function mergeVNodeHook (def, hookKey, hook) { + if (def instanceof VNode) { + def = def.data.hook || (def.data.hook = {}); + } + var invoker; + var oldHook = def[hookKey]; + + function wrappedHook () { + hook.apply(this, arguments); + // important: remove merged hook to ensure it's called only once + // and prevent memory leak + remove(invoker.fns, wrappedHook); + } + + if (isUndef(oldHook)) { + // no existing hook + invoker = createFnInvoker([wrappedHook]); + } else { + /* istanbul ignore if */ + if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { + // already a merged invoker + invoker = oldHook; + invoker.fns.push(wrappedHook); + } else { + // existing plain hook + invoker = createFnInvoker([oldHook, wrappedHook]); + } + } + + invoker.merged = true; + def[hookKey] = invoker; + } + + /* */ + + function extractPropsFromVNodeData ( + data, + Ctor, + tag + ) { + // we are only extracting raw values here. + // validation and default values are handled in the child + // component itself. + var propOptions = Ctor.options.props; + if (isUndef(propOptions)) { + return + } + var res = {}; + var attrs = data.attrs; + var props = data.props; + if (isDef(attrs) || isDef(props)) { + for (var key in propOptions) { + var altKey = hyphenate(key); + { + var keyInLowerCase = key.toLowerCase(); + if ( + key !== keyInLowerCase && + attrs && hasOwn(attrs, keyInLowerCase) + ) { + tip( + "Prop \"" + keyInLowerCase + "\" is passed to component " + + (formatComponentName(tag || Ctor)) + ", but the declared prop name is" + + " \"" + key + "\". " + + "Note that HTML attributes are case-insensitive and camelCased " + + "props need to use their kebab-case equivalents when using in-DOM " + + "templates. You should probably use \"" + altKey + "\" instead of \"" + key + "\"." + ); + } + } + checkProp(res, props, key, altKey, true) || + checkProp(res, attrs, key, altKey, false); + } + } + return res + } + + function checkProp ( + res, + hash, + key, + altKey, + preserve + ) { + if (isDef(hash)) { + if (hasOwn(hash, key)) { + res[key] = hash[key]; + if (!preserve) { + delete hash[key]; + } + return true + } else if (hasOwn(hash, altKey)) { + res[key] = hash[altKey]; + if (!preserve) { + delete hash[altKey]; + } + return true + } + } + return false + } + + /* */ + + // The template compiler attempts to minimize the need for normalization by + // statically analyzing the template at compile time. + // + // For plain HTML markup, normalization can be completely skipped because the + // generated render function is guaranteed to return Array. There are + // two cases where extra normalization is needed: + + // 1. When the children contains components - because a functional component + // may return an Array instead of a single root. In this case, just a simple + // normalization is needed - if any child is an Array, we flatten the whole + // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep + // because functional components already normalize their own children. + function simpleNormalizeChildren (children) { + for (var i = 0; i < children.length; i++) { + if (Array.isArray(children[i])) { + return Array.prototype.concat.apply([], children) + } + } + return children + } + + // 2. When the children contains constructs that always generated nested Arrays, + // e.g.