1037 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			1037 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| local QBCore = exports['qb-core']:GetCoreObject()
 | |
| local currentVehicle = nil
 | |
| local anchoredBoats = {} -- Speichert verankerte Boote
 | |
| 
 | |
| -- Boot-Klassen für spezielle Behandlung
 | |
| local boatClasses = {
 | |
|     [14] = true, -- Boats
 | |
| }
 | |
| 
 | |
| -- Sitz-Namen für bessere Anzeige
 | |
| local seatNames = {
 | |
|     [-1] = "Kapitän",
 | |
|     [0] = "Beifahrer",
 | |
|     [1] = "Hinten Links",
 | |
|     [2] = "Hinten Rechts",
 | |
|     [3] = "Hinten Mitte",
 | |
|     [4] = "Extra 1",
 | |
|     [5] = "Extra 2",
 | |
|     [6] = "Extra 3",
 | |
|     [7] = "Extra 4",
 | |
|     [8] = "Extra 5",
 | |
|     [9] = "Extra 6"
 | |
| }
 | |
| 
 | |
| -- Funktion um aktuellen Sitz zu bekommen
 | |
| local function getCurrentSeat(ped, vehicle)
 | |
|     if not IsPedInVehicle(ped, vehicle, false) then
 | |
|         return nil
 | |
|     end
 | |
|     
 | |
|     -- Prüfe Fahrersitz
 | |
|     if GetPedInVehicleSeat(vehicle, -1) == ped then
 | |
|         return -1
 | |
|     end
 | |
|     
 | |
|     -- Prüfe Passagiersitze
 | |
|     local maxSeats = GetVehicleMaxNumberOfPassengers(vehicle)
 | |
|     for i = 0, maxSeats - 1 do
 | |
|         if GetPedInVehicleSeat(vehicle, i) == ped then
 | |
|             return i
 | |
|         end
 | |
|     end
 | |
|     
 | |
|     return nil
 | |
| end
 | |
| 
 | |
| -- Prüfe ob Fahrzeug ein Boot ist
 | |
| local function isBoat(vehicle)
 | |
|     local vehicleClass = GetVehicleClass(vehicle)
 | |
|     return boatClasses[vehicleClass] or false
 | |
| end
 | |
| 
 | |
| -- Lade verankerte Boote vom Server
 | |
| local function loadAnchoredBoats()
 | |
|     QBCore.Functions.TriggerCallback('nordi_carmenu:server:getAnchoredBoats', function(serverAnchors)
 | |
|         if serverAnchors then
 | |
|             for plate, anchorData in pairs(serverAnchors) do
 | |
|                 -- Finde Fahrzeug mit dieser Kennzeichen
 | |
|                 local vehicles = GetGamePool('CVehicle')
 | |
|                 for _, vehicle in pairs(vehicles) do
 | |
|                     if DoesEntityExist(vehicle) then
 | |
|                         local vehicleProps = QBCore.Functions.GetVehicleProperties(vehicle)
 | |
|                         if vehicleProps.plate == plate and isBoat(vehicle) then
 | |
|                             -- Setze Boot an gespeicherte Position
 | |
|                             -- Korrigiere die Z-Koordinate (Höhe) für Wasseroberfläche
 | |
|                             local waterHeight = GetWaterHeightNoWaves(anchorData.coords.x, anchorData.coords.y, anchorData.coords.z)
 | |
|                             
 | |
|                             -- Wenn kein Wasser gefunden wurde, nutze eine leichte Korrektur der gespeicherten Höhe
 | |
|                             if waterHeight == 0 then
 | |
|                                 waterHeight = anchorData.coords.z - 0.5 -- Leichte Korrektur nach unten
 | |
|                             end
 | |
|                             
 | |
|                             SetEntityCoords(vehicle, anchorData.coords.x, anchorData.coords.y, waterHeight)
 | |
|                             SetEntityHeading(vehicle, anchorData.heading)
 | |
|                             FreezeEntityPosition(vehicle, true)
 | |
|                             SetEntityInvincible(vehicle, true)
 | |
|                             
 | |
|                             local vehicleNetId = NetworkGetNetworkIdFromEntity(vehicle)
 | |
|                             anchoredBoats[vehicleNetId] = anchorData
 | |
|                             
 | |
|                             print("^2[Anker]^7 Boot mit Kennzeichen " .. plate .. " wurde verankert geladen.")
 | |
|                             break
 | |
|                         end
 | |
|                     end
 | |
|                 end
 | |
|             end
 | |
|         end
 | |
|     end)
 | |
| end
 | |
| 
 | |
| -- Speichere Anker auf Server
 | |
| local function saveAnchorToServer(vehicle, anchorData)
 | |
|     local vehicleProps = QBCore.Functions.GetVehicleProperties(vehicle)
 | |
|     if vehicleProps.plate then
 | |
|         TriggerServerEvent('nordi_carmenu:server:saveAnchor', vehicleProps.plate, anchorData)
 | |
|     end
 | |
| end
 | |
| 
 | |
| -- Entferne Anker vom Server
 | |
| local function removeAnchorFromServer(vehicle)
 | |
|     local vehicleProps = QBCore.Functions.GetVehicleProperties(vehicle)
 | |
|     if vehicleProps.plate then
 | |
|         TriggerServerEvent('nordi_carmenu:server:removeAnchor', vehicleProps.plate)
 | |
|     end
 | |
| end
 | |
| 
 | |
| -- Anker setzen/entfernen
 | |
| local function toggleAnchor(vehicle)
 | |
|     if not isBoat(vehicle) then
 | |
|         QBCore.Functions.Notify('Nur Boote können verankert werden!', 'error')
 | |
|         return
 | |
|     end
 | |
|     
 | |
|     local vehicleNetId = NetworkGetNetworkIdFromEntity(vehicle)
 | |
|     
 | |
|     if anchoredBoats[vehicleNetId] then
 | |
|         -- Anker lichten (ohne Wackel-Effekt)
 | |
|         FreezeEntityPosition(vehicle, false)
 | |
|         SetEntityInvincible(vehicle, false)
 | |
|         anchoredBoats[vehicleNetId] = nil
 | |
|         
 | |
|         -- Entferne vom Server
 | |
|         removeAnchorFromServer(vehicle)
 | |
|         
 | |
|         QBCore.Functions.Notify('⚓ Anker gelichtet! Das Boot kann wieder bewegt werden.', 'success')
 | |
|     else
 | |
| -- Anker werfen (ohne Sound und Effekte)
 | |
| local coords = GetEntityCoords(vehicle)
 | |
| local heading = GetEntityHeading(vehicle)
 | |
| 
 | |
| -- Keine Höhenanpassung - Boot bleibt genau da, wo es ist
 | |
| FreezeEntityPosition(vehicle, true)
 | |
| SetEntityInvincible(vehicle, true)
 | |
| 
 | |
| local anchorData = {
 | |
|     coords = coords,
 | |
|     heading = heading,
 | |
|     time = GetGameTimer()
 | |
| }
 | |
| 
 | |
| anchoredBoats[vehicleNetId] = anchorData
 | |
| 
 | |
| -- Speichere auf Server
 | |
| saveAnchorToServer(vehicle, anchorData)
 | |
| 
 | |
| QBCore.Functions.Notify('⚓ Anker geworfen! Das Boot ist jetzt verankert.', 'success')
 | |
| 
 | |
| 
 | |
| QBCore.Functions.Notify('⚓ Anker geworfen! Das Boot ist jetzt verankert.', 'success')
 | |
|     end
 | |
| end
 | |
| 
 | |
| -- Prüfe ob Boot verankert ist
 | |
| local function isBoatAnchored(vehicle)
 | |
|     local vehicleNetId = NetworkGetNetworkIdFromEntity(vehicle)
 | |
|     return anchoredBoats[vehicleNetId] ~= nil
 | |
| end
 | |
| 
 | |
| -- Überwache gespawnte Fahrzeuge für Anker-Wiederherstellung
 | |
| CreateThread(function()
 | |
|     local checkedVehicles = {}
 | |
|     
 | |
|     while true do
 | |
|         local vehicles = GetGamePool('CVehicle')
 | |
|         
 | |
|         for _, vehicle in pairs(vehicles) do
 | |
|             if DoesEntityExist(vehicle) and not checkedVehicles[vehicle] then
 | |
|                 checkedVehicles[vehicle] = true
 | |
|                 
 | |
|                 if isBoat(vehicle) then
 | |
|                     -- Prüfe ob dieses Boot einen gespeicherten Anker hat
 | |
|                     local vehicleProps = QBCore.Functions.GetVehicleProperties(vehicle)
 | |
|                     if vehicleProps.plate then
 | |
|                         QBCore.Functions.TriggerCallback('nordi_carmenu:server:getAnchorByPlate', function(anchorData)
 | |
|                             if anchorData then
 | |
|                                 -- Finde die Wasseroberfläche unter dem Boot
 | |
|                                 local waterHeight = GetWaterHeightNoWaves(anchorData.coords.x, anchorData.coords.y, anchorData.coords.z)
 | |
|                                 
 | |
|                                 -- Wenn kein Wasser gefunden wurde, nutze eine leichte Korrektur der gespeicherten Höhe
 | |
|                                 if waterHeight == 0 then
 | |
|                                     waterHeight = anchorData.coords.z - 0.5 -- Leichte Korrektur nach unten
 | |
|                                 end
 | |
|                                 
 | |
|                                 -- Setze Boot an gespeicherte Position mit korrigierter Höhe
 | |
|                                 SetEntityCoords(vehicle, anchorData.coords.x, anchorData.coords.y, waterHeight)
 | |
|                                 SetEntityHeading(vehicle, anchorData.heading)
 | |
|                                 FreezeEntityPosition(vehicle, true)
 | |
|                                 SetEntityInvincible(vehicle, true)
 | |
|                                 
 | |
|                                 local vehicleNetId = NetworkGetNetworkIdFromEntity(vehicle)
 | |
|                                 anchoredBoats[vehicleNetId] = anchorData
 | |
|                                 
 | |
|                                 print("^2[Anker]^7 Boot " .. vehicleProps.plate .. " automatisch verankert.")
 | |
|                             end
 | |
|                         end, vehicleProps.plate)
 | |
|                     end
 | |
|                 end
 | |
|             end
 | |
|         end
 | |
|         
 | |
|         -- Cleanup nicht mehr existierende Fahrzeuge
 | |
|         for vehicle, _ in pairs(checkedVehicles) do
 | |
|             if not DoesEntityExist(vehicle) then
 | |
|                 checkedVehicles[vehicle] = nil
 | |
|             end
 | |
|         end
 | |
|         
 | |
|         Wait(5000) -- Prüfe alle 5 Sekunden
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Funktion um verfügbare Sitze zu bekommen
 | |
| local function getAvailableSeats(vehicle)
 | |
|     local seats = {}
 | |
|     local maxSeats = GetVehicleMaxNumberOfPassengers(vehicle)
 | |
|     
 | |
|     -- Fahrersitz (-1)
 | |
|     if IsVehicleSeatFree(vehicle, -1) then
 | |
|         local seatName = isBoat(vehicle) and seatNames[-1] or "Fahrer"
 | |
|         table.insert(seats, {
 | |
|             index = -1,
 | |
|             name = seatName,
 | |
|             icon = isBoat(vehicle) and "fas fa-anchor" or "fas fa-steering-wheel"
 | |
|         })
 | |
|     end
 | |
|     
 | |
|     -- Passagiersitze (0 bis maxSeats-1)
 | |
|     for i = 0, maxSeats - 1 do
 | |
|         if IsVehicleSeatFree(vehicle, i) then
 | |
|             table.insert(seats, {
 | |
|                 index = i,
 | |
|                 name = seatNames[i] or "Sitz " .. (i + 1),
 | |
|                 icon = "fas fa-user"
 | |
|             })
 | |
|         end
 | |
|     end
 | |
|     
 | |
|     return seats
 | |
| end
 | |
| 
 | |
| -- Funktion um belegte Sitze zu bekommen
 | |
| local function getOccupiedSeats(vehicle)
 | |
|     local occupiedSeats = {}
 | |
|     local maxSeats = GetVehicleMaxNumberOfPassengers(vehicle)
 | |
|     
 | |
|     -- Fahrersitz prüfen
 | |
|     if not IsVehicleSeatFree(vehicle, -1) then
 | |
|         local ped = GetPedInVehicleSeat(vehicle, -1)
 | |
|         local playerName = "Unbekannt"
 | |
|         
 | |
|         if IsPedAPlayer(ped) then
 | |
|             local playerId = NetworkGetPlayerIndexFromPed(ped)
 | |
|             if playerId ~= -1 then
 | |
|                 playerName = GetPlayerName(playerId)
 | |
|             end
 | |
|         else
 | |
|             playerName = "NPC"
 | |
|         end
 | |
|         
 | |
|         local seatName = isBoat(vehicle) and seatNames[-1] or "Fahrer"
 | |
|         table.insert(occupiedSeats, {
 | |
|             index = -1,
 | |
|             name = seatName,
 | |
|             occupant = playerName,
 | |
|             icon = isBoat(vehicle) and "fas fa-anchor" or "fas fa-steering-wheel"
 | |
|         })
 | |
|     end
 | |
|     
 | |
|     -- Passagiersitze prüfen
 | |
|     for i = 0, maxSeats - 1 do
 | |
|         if not IsVehicleSeatFree(vehicle, i) then
 | |
|             local ped = GetPedInVehicleSeat(vehicle, i)
 | |
|             local playerName = "Unbekannt"
 | |
|             
 | |
|             if IsPedAPlayer(ped) then
 | |
|                 local playerId = NetworkGetPlayerIndexFromPed(ped)
 | |
|                 if playerId ~= -1 then
 | |
|                     playerName = GetPlayerName(playerId)
 | |
|                 end
 | |
|             else
 | |
|                 playerName = "NPC"
 | |
|             end
 | |
|             
 | |
|             table.insert(occupiedSeats, {
 | |
|                 index = i,
 | |
|                 name = seatNames[i] or "Sitz " .. (i + 1),
 | |
|                 occupant = playerName,
 | |
|                 icon = "fas fa-user"
 | |
|             })
 | |
|         end
 | |
|     end
 | |
|     
 | |
|     return occupiedSeats
 | |
| end
 | |
| 
 | |
| -- Funktion zum direkten Einsteigen (für Boote)
 | |
| local function teleportToSeat(vehicle, seatIndex)
 | |
|     local playerPed = PlayerPedId()
 | |
|     
 | |
|     if IsPedInAnyVehicle(playerPed, false) then
 | |
|         QBCore.Functions.Notify('Du bist bereits in einem Fahrzeug!', 'error')
 | |
|         return
 | |
|     end
 | |
|     
 | |
|     if not IsVehicleSeatFree(vehicle, seatIndex) then
 | |
|         QBCore.Functions.Notify('Dieser Sitz ist bereits belegt!', 'error')
 | |
|         return
 | |
|     end
 | |
|     
 | |
|     -- Prüfe ob Fahrzeug abgeschlossen ist
 | |
|     if GetVehicleDoorLockStatus(vehicle) == 2 then
 | |
|         QBCore.Functions.Notify('Das Fahrzeug ist abgeschlossen!', 'error')
 | |
|         return
 | |
|     end
 | |
|     
 | |
|     -- Direkte Teleportation ohne Animation
 | |
|     SetPedIntoVehicle(playerPed, vehicle, seatIndex)
 | |
|     currentVehicle = vehicle
 | |
|     
 | |
|     local seatName = seatNames[seatIndex] or "Sitz " .. (seatIndex + 1)
 | |
|     if isBoat(vehicle) then
 | |
|         QBCore.Functions.Notify('Willkommen an Bord! (' .. seatName .. ')', 'success')
 | |
|         
 | |
|         -- Zeige Anker-Status wenn Kapitän
 | |
|         if seatIndex == -1 and isBoatAnchored(vehicle) then
 | |
|             QBCore.Functions.Notify('⚓ Das Boot ist verankert. Nutze das Menü um den Anker zu lichten.', 'primary')
 | |
|         end
 | |
|     else
 | |
|         QBCore.Functions.Notify('Du bist eingestiegen (' .. seatName .. ')', 'success')
 | |
|     end
 | |
| end
 | |
| 
 | |
| -- Funktion zum normalen Einsteigen (für Autos)
 | |
| local function enterVehicleSeat(vehicle, seatIndex)
 | |
|     local playerPed = PlayerPedId()
 | |
|     
 | |
|     if IsPedInAnyVehicle(playerPed, false) then
 | |
|         QBCore.Functions.Notify('Du bist bereits in einem Fahrzeug!', 'error')
 | |
|         return
 | |
|     end
 | |
|     
 | |
|     if not IsVehicleSeatFree(vehicle, seatIndex) then
 | |
|         QBCore.Functions.Notify('Dieser Sitz ist bereits belegt!', 'error')
 | |
|         return
 | |
|     end
 | |
|     
 | |
|     -- Prüfe ob Fahrzeug abgeschlossen ist
 | |
|     if GetVehicleDoorLockStatus(vehicle) == 2 then
 | |
|         QBCore.Functions.Notify('Das Fahrzeug ist abgeschlossen!', 'error')
 | |
|         return
 | |
|     end
 | |
|     
 | |
|     TaskEnterVehicle(playerPed, vehicle, 10000, seatIndex, 1.0, 1, 0)
 | |
|     currentVehicle = vehicle
 | |
|     
 | |
|     local seatName = seatNames[seatIndex] or "Sitz " .. (seatIndex + 1)
 | |
|     QBCore.Functions.Notify('Steige in das Fahrzeug ein (' .. seatName .. ')...', 'primary')
 | |
| end
 | |
| 
 | |
| -- Funktion zum Sitzwechsel
 | |
| local function switchSeat(vehicle, newSeatIndex)
 | |
|     local playerPed = PlayerPedId()
 | |
|     
 | |
|     if not IsPedInVehicle(playerPed, vehicle, false) then
 | |
|         QBCore.Functions.Notify('Du bist nicht in diesem Fahrzeug!', 'error')
 | |
|         return
 | |
|     end
 | |
|     
 | |
|     if not IsVehicleSeatFree(vehicle, newSeatIndex) then
 | |
|         QBCore.Functions.Notify('Dieser Sitz ist bereits belegt!', 'error')
 | |
|         return
 | |
|     end
 | |
|     
 | |
|     -- Für Boote: Direkte Teleportation
 | |
|     if isBoat(vehicle) then
 | |
|         SetPedIntoVehicle(playerPed, vehicle, newSeatIndex)
 | |
|     else
 | |
|         -- Für Autos: Normale Animation
 | |
|         TaskShuffleToNextVehicleSeat(playerPed, vehicle)
 | |
|         Wait(100)
 | |
|         SetPedIntoVehicle(playerPed, vehicle, newSeatIndex)
 | |
|     end
 | |
|     
 | |
|     local seatName = seatNames[newSeatIndex] or "Sitz " .. (newSeatIndex + 1)
 | |
|     QBCore.Functions.Notify('Gewechselt zu: ' .. seatName, 'success')
 | |
|     
 | |
|     -- Zeige Anker-Status wenn zum Kapitän gewechselt
 | |
|     if isBoat(vehicle) and newSeatIndex == -1 and isBoatAnchored(vehicle) then
 | |
|         QBCore.Functions.Notify('⚓ Das Boot ist verankert. Nutze das Menü um den Anker zu lichten.', 'primary')
 | |
|     end
 | |
| end
 | |
| 
 | |
| -- Tür-Kontrollfunktion
 | |
| local function controlDoor(vehicle, doorIndex)
 | |
|     if GetVehicleDoorAngleRatio(vehicle, doorIndex) > 0.0 then
 | |
|         SetVehicleDoorShut(vehicle, doorIndex, false)
 | |
|         QBCore.Functions.Notify('Tür geschlossen', 'success')
 | |
|     else
 | |
|         SetVehicleDoorOpen(vehicle, doorIndex, false, false)
 | |
|         QBCore.Functions.Notify('Tür geöffnet', 'success')
 | |
|     end
 | |
|     -- Menü nach kurzer Verzögerung neu öffnen, damit die Benachrichtigung sichtbar ist
 | |
|     Wait(100)
 | |
|     showDoorControlMenu(vehicle)
 | |
| end
 | |
| 
 | |
| -- Fenster-Kontrollfunktion
 | |
| local function controlWindow(vehicle, windowIndex)
 | |
|     if IsVehicleWindowIntact(vehicle, windowIndex) then
 | |
|         RollDownWindow(vehicle, windowIndex)
 | |
|         QBCore.Functions.Notify('Fenster geöffnet', 'success')
 | |
|     else
 | |
|         RollUpWindow(vehicle, windowIndex)
 | |
|         QBCore.Functions.Notify('Fenster geschlossen', 'success')
 | |
|     end
 | |
|     -- Menü nach kurzer Verzögerung neu öffnen, damit die Benachrichtigung sichtbar ist
 | |
|     Wait(100)
 | |
|     showWindowControlMenu(vehicle)
 | |
| end
 | |
| 
 | |
| -- Extras-Kontrollfunktion
 | |
| local function controlExtra(vehicle, extraId)
 | |
|     if DoesExtraExist(vehicle, extraId) then
 | |
|         if IsVehicleExtraTurnedOn(vehicle, extraId) then
 | |
|             SetVehicleExtra(vehicle, extraId, true)
 | |
|             QBCore.Functions.Notify('Extra ' .. extraId .. ' deaktiviert', 'success')
 | |
|         else
 | |
|             SetVehicleExtra(vehicle, extraId, false)
 | |
|             QBCore.Functions.Notify('Extra ' .. extraId .. ' aktiviert', 'success')
 | |
|         end
 | |
|     end
 | |
| end
 | |
| 
 | |
| -- Türen-Kontrollmenü
 | |
| function showDoorControlMenu(vehicle)
 | |
|     local options = {
 | |
|         {
 | |
|             title = '🚪 Alle Türen',
 | |
|             description = 'Alle Türen öffnen/schließen',
 | |
|             icon = 'fas fa-door-open',
 | |
|             onSelect = function()
 | |
|                 for i = 0, 5 do
 | |
|                     if GetVehicleDoorAngleRatio(vehicle, i) > 0.0 then
 | |
|                         SetVehicleDoorShut(vehicle, i, false)
 | |
|                     else
 | |
|                         SetVehicleDoorOpen(vehicle, i, false, false)
 | |
|                     end
 | |
|                 end
 | |
|                 QBCore.Functions.Notify('Alle Türen umgeschaltet', 'success')
 | |
|                 -- Menü nach kurzer Verzögerung neu öffnen
 | |
|                 Wait(100)
 | |
|                 showDoorControlMenu(vehicle)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '🚪 Fahrertür',
 | |
|             description = 'Fahrertür öffnen/schließen',
 | |
|             icon = 'fas fa-door-open',
 | |
|             onSelect = function()
 | |
|                 controlDoor(vehicle, 0)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '🚪 Beifahrertür',
 | |
|             description = 'Beifahrertür öffnen/schließen',
 | |
|             icon = 'fas fa-door-open',
 | |
|             onSelect = function()
 | |
|                 controlDoor(vehicle, 1)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '🚪 Hinten Links',
 | |
|             description = 'Hintere linke Tür öffnen/schließen',
 | |
|             icon = 'fas fa-door-open',
 | |
|             onSelect = function()
 | |
|                 controlDoor(vehicle, 2)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '🚪 Hinten Rechts',
 | |
|             description = 'Hintere rechte Tür öffnen/schließen',
 | |
|             icon = 'fas fa-door-open',
 | |
|             onSelect = function()
 | |
|                 controlDoor(vehicle, 3)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '🚪 Motorhaube',
 | |
|             description = 'Motorhaube öffnen/schließen',
 | |
|             icon = 'fas fa-car',
 | |
|             onSelect = function()
 | |
|                 controlDoor(vehicle, 4)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '🚪 Kofferraum',
 | |
|             description = 'Kofferraum öffnen/schließen',
 | |
|             icon = 'fas fa-box',
 | |
|             onSelect = function()
 | |
|                 controlDoor(vehicle, 5)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '← Zurück',
 | |
|             description = 'Zurück zum Hauptmenü',
 | |
|             icon = 'fas fa-arrow-left',
 | |
|             onSelect = function()
 | |
|                 showVehicleControlMenu(vehicle)
 | |
|             end
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     lib.registerContext({
 | |
|         id = 'vehicle_door_menu',
 | |
|         title = '🚪 Türen steuern',
 | |
|         options = options
 | |
|     })
 | |
| 
 | |
|     lib.showContext('vehicle_door_menu')
 | |
| end
 | |
| 
 | |
| -- Fenster-Kontrollmenü
 | |
| function showWindowControlMenu(vehicle)
 | |
|     local options = {
 | |
|         {
 | |
|             title = '🪟 Alle Fenster',
 | |
|             description = 'Alle Fenster öffnen/schließen',
 | |
|             icon = 'fas fa-window-maximize',
 | |
|             onSelect = function()
 | |
|                 for i = 0, 3 do
 | |
|                     if IsVehicleWindowIntact(vehicle, i) then
 | |
|                         RollDownWindow(vehicle, i)
 | |
|                     else
 | |
|                         RollUpWindow(vehicle, i)
 | |
|                     end
 | |
|                 end
 | |
|                 QBCore.Functions.Notify('Alle Fenster umgeschaltet', 'success')
 | |
|                 -- Menü nach kurzer Verzögerung neu öffnen
 | |
|                 Wait(100)
 | |
|                 showWindowControlMenu(vehicle)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '🪟 Fahrerfenster',
 | |
|             description = 'Fahrerfenster öffnen/schließen',
 | |
|             icon = 'fas fa-window-maximize',
 | |
|             onSelect = function()
 | |
|                 controlWindow(vehicle, 0)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '🪟 Beifahrerfenster',
 | |
|             description = 'Beifahrerfenster öffnen/schließen',
 | |
|             icon = 'fas fa-window-maximize',
 | |
|             onSelect = function()
 | |
|                 controlWindow(vehicle, 1)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '🪟 Hinten Links',
 | |
|             description = 'Hinteres linkes Fenster öffnen/schließen',
 | |
|             icon = 'fas fa-window-maximize',
 | |
|             onSelect = function()
 | |
|                 controlWindow(vehicle, 2)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '🪟 Hinten Rechts',
 | |
|             description = 'Hinteres rechtes Fenster öffnen/schließen',
 | |
|             icon = 'fas fa-window-maximize',
 | |
|             onSelect = function()
 | |
|                 controlWindow(vehicle, 3)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '← Zurück',
 | |
|             description = 'Zurück zum Hauptmenü',
 | |
|             icon = 'fas fa-arrow-left',
 | |
|             onSelect = function()
 | |
|                 showVehicleControlMenu(vehicle)
 | |
|             end
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     lib.registerContext({
 | |
|         id = 'vehicle_window_menu',
 | |
|         title = '🪟 Fenster steuern',
 | |
|         options = options
 | |
|     })
 | |
| 
 | |
|     lib.showContext('vehicle_window_menu')
 | |
| end
 | |
| 
 | |
| -- Extras-Kontrollmenü
 | |
| function showExtrasControlMenu(vehicle)
 | |
|     local options = {}
 | |
|     
 | |
|     -- Prüfe welche Extras existieren
 | |
|     local hasExtras = false
 | |
|     for i = 0, 14 do
 | |
|         if DoesExtraExist(vehicle, i) then
 | |
|             hasExtras = true
 | |
|             local status = IsVehicleExtraTurnedOn(vehicle, i) and "Aktiviert" or "Deaktiviert"
 | |
|             table.insert(options, {
 | |
|                 title = '🔧 Extra ' .. i,
 | |
|                 description = 'Status: ' .. status,
 | |
|                 icon = 'fas fa-puzzle-piece',
 | |
|                 onSelect = function()
 | |
|                     controlExtra(vehicle, i)
 | |
|                     showExtrasControlMenu(vehicle) -- Aktualisiere Menü für aktuellen Status
 | |
|                 end
 | |
|             })
 | |
|         end
 | |
|     end
 | |
|     
 | |
|     if not hasExtras then
 | |
|         table.insert(options, {
 | |
|             title = 'Keine Extras verfügbar',
 | |
|             description = 'Dieses Fahrzeug hat keine Extras',
 | |
|             icon = 'fas fa-times',
 | |
|             disabled = true
 | |
|         })
 | |
|     end
 | |
|     
 | |
|     table.insert(options, {
 | |
|         title = '← Zurück',
 | |
|         description = 'Zurück zum Hauptmenü',
 | |
|         icon = 'fas fa-arrow-left',
 | |
|         onSelect = function()
 | |
|             showVehicleControlMenu(vehicle)
 | |
|         end
 | |
|     })
 | |
| 
 | |
|     lib.registerContext({
 | |
|         id = 'vehicle_extras_menu',
 | |
|         title = '🔧 Extras steuern',
 | |
|         options = options
 | |
|     })
 | |
| 
 | |
|     lib.showContext('vehicle_extras_menu')
 | |
| end
 | |
| 
 | |
| -- Hauptmenü für Fahrzeugsteuerung
 | |
| function showVehicleControlMenu(vehicle)
 | |
|     local options = {
 | |
|         {
 | |
|             title = '🚪 Türen steuern',
 | |
|             description = 'Türen öffnen/schließen',
 | |
|             icon = 'fas fa-door-open',
 | |
|             onSelect = function()
 | |
|                 showDoorControlMenu(vehicle)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '🪟 Fenster steuern',
 | |
|             description = 'Fenster öffnen/schließen',
 | |
|             icon = 'fas fa-window-maximize',
 | |
|             onSelect = function()
 | |
|                 showWindowControlMenu(vehicle)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '🔧 Extras steuern',
 | |
|             description = 'Fahrzeug-Extras ein/ausschalten',
 | |
|             icon = 'fas fa-puzzle-piece',
 | |
|             onSelect = function()
 | |
|                 showExtrasControlMenu(vehicle)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '👥 Sitzplätze',
 | |
|             description = 'Sitzplatz wechseln oder aussteigen',
 | |
|             icon = 'fas fa-chair',
 | |
|             onSelect = function()
 | |
|                 showSeatMenu(vehicle)
 | |
|             end
 | |
|         },
 | |
|         {
 | |
|             title = '📋 Fahrzeuginfo',
 | |
|             description = 'Informationen über das Fahrzeug',
 | |
|             icon = 'fas fa-info-circle',
 | |
|             onSelect = function()
 | |
|                 showVehicleInfo(vehicle)
 | |
|             end
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     -- Boot-spezifische Anker-Option
 | |
|     if isBoat(vehicle) then
 | |
|         local isAnchored = isBoatAnchored(vehicle)
 | |
|         local playerPed = PlayerPedId()
 | |
|         local currentSeat = getCurrentSeat(playerPed, vehicle)
 | |
|         
 | |
|         if currentSeat == -1 then -- Nur Kapitän kann Anker bedienen
 | |
|             table.insert(options, 1, {
 | |
|                 title = isAnchored and '⚓ Anker lichten' or '⚓ Anker werfen',
 | |
|                 description = isAnchored and 'Boot wieder beweglich machen' or 'Boot an aktueller Position verankern',
 | |
|                 icon = 'fas fa-anchor',
 | |
|                 onSelect = function()
 | |
|                     toggleAnchor(vehicle)
 | |
|                     showVehicleControlMenu(vehicle) -- Aktualisiere Menü für aktuellen Status
 | |
|                 end
 | |
|             })
 | |
|         end
 | |
|     end
 | |
| 
 | |
|     lib.registerContext({
 | |
|         id = 'vehicle_control_menu',
 | |
|         title = '🚗 Fahrzeugsteuerung',
 | |
|         options = options
 | |
|     })
 | |
| 
 | |
|     lib.showContext('vehicle_control_menu')
 | |
| end
 | |
| 
 | |
| -- Hauptmenü für Sitzauswahl
 | |
| function showSeatMenu(vehicle)
 | |
|     local playerPed = PlayerPedId()
 | |
|     local isInVehicle = IsPedInVehicle(playerPed, vehicle, false)
 | |
|     local availableSeats = getAvailableSeats(vehicle)
 | |
|     local occupiedSeats = getOccupiedSeats(vehicle)
 | |
|     local isVehicleBoat = isBoat(vehicle)
 | |
|     local isAnchored = isBoatAnchored(vehicle)
 | |
|     local options = {}
 | |
|     
 | |
|     -- Header
 | |
|     local vehicleName = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle))
 | |
|     local vehicleLabel = GetLabelText(vehicleName)
 | |
|     if vehicleLabel == "NULL" then
 | |
|         vehicleLabel = vehicleName
 | |
|     end
 | |
|     
 | |
|     -- Boot-spezifische Anzeige mit Anker-Status
 | |
|     if isVehicleBoat then
 | |
|         vehicleLabel = (isAnchored and "⚓ " or "⛵ ") .. vehicleLabel
 | |
|     else
 | |
|         vehicleLabel = "🚗 " .. vehicleLabel
 | |
|     end
 | |
|     
 | |
|     -- Anker-Kontrolle (nur für Boote und nur wenn im Boot)
 | |
|     if isVehicleBoat and isInVehicle then
 | |
|         local currentSeat = getCurrentSeat(playerPed, vehicle)
 | |
|         -- Nur Kapitän kann Anker bedienen
 | |
|         if currentSeat == -1 then
 | |
|             table.insert(options, {
 | |
|                 title = isAnchored and '⚓ Anker lichten' or '⚓ Anker werfen',
 | |
|                 description = isAnchored and 'Boot wieder beweglich machen' or 'Boot an aktueller Position verankern',
 | |
|                 icon = 'fas fa-anchor',
 | |
|                 onSelect = function()
 | |
|                     toggleAnchor(vehicle)
 | |
|                 end
 | |
|             })
 | |
|             
 | |
|             -- Trennlinie
 | |
|             table.insert(options, {
 | |
|                 title = '─────────────────',
 | |
|                 disabled = true
 | |
|             })
 | |
|         end
 | |
|     end
 | |
|     
 | |
|     -- Verfügbare Sitze
 | |
|     if #availableSeats > 0 then
 | |
|         local headerText = isVehicleBoat and '🟢 Verfügbare Plätze' or '🟢 Verfügbare Sitze'
 | |
|         table.insert(options, {
 | |
|             title = headerText,
 | |
|             description = isVehicleBoat and 'Freie Plätze an Bord' or 'Freie Sitzplätze',
 | |
|             disabled = true
 | |
|         })
 | |
|         
 | |
|         for _, seat in pairs(availableSeats) do
 | |
|             local actionText = isInVehicle and 'Zu diesem Platz wechseln' or (isVehicleBoat and 'An Bord gehen' or 'In das Fahrzeug einsteigen')
 | |
|             table.insert(options, {
 | |
|                 title = seat.name,
 | |
|                 description = actionText,
 | |
|                 icon = seat.icon,
 | |
|                 onSelect = function()
 | |
|                     if isInVehicle then
 | |
|                         switchSeat(vehicle, seat.index)
 | |
|                     else
 | |
|                         if isVehicleBoat then
 | |
|                             teleportToSeat(vehicle, seat.index)
 | |
|                         else
 | |
|                             enterVehicleSeat(vehicle, seat.index)
 | |
|                         end
 | |
|                     end
 | |
|                 end
 | |
|             })
 | |
|         end
 | |
|     end
 | |
|     
 | |
|     -- Belegte Sitze anzeigen
 | |
|     if #occupiedSeats > 0 then
 | |
|         local headerText = isVehicleBoat and '🔴 Belegte Plätze' or '🔴 Belegte Sitze'
 | |
|         table.insert(options, {
 | |
|             title = headerText,
 | |
|             description = isVehicleBoat and 'Bereits besetzte Plätze' or 'Bereits besetzte Sitzplätze',
 | |
|             disabled = true
 | |
|         })
 | |
|         
 | |
|         for _, seat in pairs(occupiedSeats) do
 | |
|             table.insert(options, {
 | |
|                 title = seat.name,
 | |
|                 description = 'Besetzt von: ' .. seat.occupant,
 | |
|                 icon = seat.icon,
 | |
|                 disabled = true
 | |
|             })
 | |
|         end
 | |
|     end
 | |
|     
 | |
|     -- Aussteigen Option (nur wenn im Fahrzeug)
 | |
|     if isInVehicle then
 | |
|         local exitText = isVehicleBoat and '🚪 Von Bord gehen' or '🚪 Aussteigen'
 | |
|         local exitDesc = isVehicleBoat and 'Das Boot verlassen' or 'Das Fahrzeug verlassen'
 | |
|         table.insert(options, {
 | |
|             title = exitText,
 | |
|             description = exitDesc,
 | |
|             icon = 'fas fa-door-open',
 | |
|             onSelect = function()
 | |
|                 TaskLeaveVehicle(playerPed, vehicle, 0)
 | |
|                 local exitMsg = isVehicleBoat and 'Du gehst von Bord...' or 'Du steigst aus dem Fahrzeug aus...'
 | |
|                 QBCore.Functions.Notify(exitMsg, 'primary')
 | |
|             end
 | |
|         })
 | |
|     end
 | |
|     
 | |
|     -- Zurück zum Hauptmenü
 | |
|     table.insert(options, {
 | |
|         title = '← Zurück zum Hauptmenü',
 | |
|         description = 'Zurück zur Fahrzeugsteuerung',
 | |
|         icon = 'fas fa-arrow-left',
 | |
|         onSelect = function()
 | |
|             showVehicleControlMenu(vehicle)
 | |
|         end
 | |
|     })
 | |
|     
 | |
|     if #options == 0 then
 | |
|         QBCore.Functions.Notify('Keine verfügbaren Aktionen!', 'error')
 | |
|         return
 | |
|     end
 | |
|     
 | |
|     lib.registerContext({
 | |
|         id = 'vehicle_seat_menu',
 | |
|         title = vehicleLabel,
 | |
|         options = options
 | |
|     })
 | |
|     
 | |
|     lib.showContext('vehicle_seat_menu')
 | |
| end
 | |
| 
 | |
| -- Fahrzeuginfo anzeigen
 | |
| function showVehicleInfo(vehicle)
 | |
|     local vehicleProps = QBCore.Functions.GetVehicleProperties(vehicle)
 | |
|     local vehicleName = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle))
 | |
|     local vehicleLabel = GetLabelText(vehicleName)
 | |
|     if vehicleLabel == "NULL" then
 | |
|         vehicleLabel = vehicleName
 | |
|     end
 | |
|     
 | |
|     local maxSeats = GetVehicleMaxNumberOfPassengers(vehicle) + 1 -- +1 für Fahrer
 | |
|     local engineHealth = math.floor(GetVehicleEngineHealth(vehicle) / 10)
 | |
|     local bodyHealth = math.floor(GetVehicleBodyHealth(vehicle) / 10)
 | |
|     local isVehicleBoat = isBoat(vehicle)
 | |
|     local isAnchored = isBoatAnchored(vehicle)
 | |
|     
 | |
|     local options = {
 | |
|         {
 | |
|             title = (isVehicleBoat and 'Boot: ' or 'Fahrzeug: ') .. vehicleLabel,
 | |
|             description = 'Kennzeichen: ' .. (vehicleProps.plate or 'Unbekannt'),
 | |
|             icon = isVehicleBoat and 'fas fa-ship' or 'fas fa-car',
 | |
|             disabled = true
 | |
|         },
 | |
|         {
 | |
|             title = (isVehicleBoat and 'Plätze: ' or 'Sitzplätze: ') .. maxSeats,
 | |
|             description = 'Maximale Anzahl Personen',
 | |
|             icon = 'fas fa-users',
 | |
|             disabled = true
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     -- Anker-Status für Boote
 | |
|     if isVehicleBoat then
 | |
|         table.insert(options, {
 | |
|             title = 'Anker-Status: ' .. (isAnchored and 'Verankert ⚓' or 'Gelichtet ⛵'),
 | |
|             description = isAnchored and 'Das Boot ist an der aktuellen Position verankert' or 'Das Boot kann frei bewegt werden',
 | |
|             icon = 'fas fa-anchor',
 | |
|             disabled = true
 | |
|         })
 | |
|     end
 | |
|     
 | |
|     table.insert(options, {
 | |
|         title = (isVehicleBoat and 'Motor: ' or 'Motor: ') .. engineHealth .. '%',
 | |
|         description = 'Zustand des Motors',
 | |
|         icon = 'fas fa-cog',
 | |
|         disabled = true
 | |
|     })
 | |
|     
 | |
|     table.insert(options, {
 | |
|         title = (isVehicleBoat and 'Rumpf: ' or 'Karosserie: ') .. bodyHealth .. '%',
 | |
|         description = isVehicleBoat and 'Zustand des Rumpfes' or 'Zustand der Karosserie',
 | |
|         icon = isVehicleBoat and 'fas fa-ship' or 'fas fa-car-crash',
 | |
|         disabled = true
 | |
|     })
 | |
|     
 | |
|     -- Kraftstoff nur für Nicht-Boote
 | |
|     if not isVehicleBoat then
 | |
|         local fuelLevel = math.floor(GetVehicleFuelLevel(vehicle))
 | |
|         table.insert(options, {
 | |
|             title = 'Kraftstoff: ' .. fuelLevel .. '%',
 | |
|             description = 'Aktueller Tankstand',
 | |
|             icon = 'fas fa-gas-pump',
 | |
|             disabled = true
 | |
|         })
 | |
|     end
 | |
|     
 | |
|     table.insert(options, {
 | |
|         title = '← Zurück',
 | |
|         description = 'Zurück zum Hauptmenü',
 | |
|         icon = 'fas fa-arrow-left',
 | |
|         onSelect = function()
 | |
|             showVehicleControlMenu(vehicle)
 | |
|         end
 | |
|     })
 | |
|     
 | |
|     lib.registerContext({
 | |
|         id = 'vehicle_info_menu',
 | |
|         title = '📋 Fahrzeuginfo',
 | |
|         options = options
 | |
|     })
 | |
|     
 | |
|     lib.showContext('vehicle_info_menu')
 | |
| end
 | |
| 
 | |
| -- QB-Target Setup (korrigierte Version)
 | |
| CreateThread(function()
 | |
|     -- Entferne zuerst alle vorhandenen Target-Optionen für Fahrzeuge, die wir gesetzt haben
 | |
|     exports['qb-target']:RemoveGlobalVehicle({
 | |
|         'vehicle:openSeatMenu',
 | |
|         'vehicle:openControlMenu'
 | |
|     })
 | |
|     
 | |
|     -- Registriere nur einen neuen Target-Punkt
 | |
|     exports['qb-target']:AddGlobalVehicle({
 | |
|         options = {
 | |
|             {
 | |
|                 type = "client",
 | |
|                 event = "vehicle:openControlMenu",
 | |
|                 icon = "fas fa-car",
 | |
|                 label = "Fahrzeug steuern",
 | |
|             }
 | |
|         },
 | |
|         distance = 3.0
 | |
|     })
 | |
| end)
 | |
| 
 | |
| -- Event Handler
 | |
| RegisterNetEvent('vehicle:openControlMenu', function(data)
 | |
|     local vehicle = data.entity
 | |
|     if DoesEntityExist(vehicle) and IsEntityAVehicle(vehicle) then
 | |
|         showVehicleControlMenu(vehicle)
 | |
|     else
 | |
|         QBCore.Functions.Notify('Kein gültiges Fahrzeug gefunden!', 'error')
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Entferne den alten Event Handler, falls er noch existiert
 | |
| RegisterNetEvent('vehicle:openSeatMenu', function(data)
 | |
|     local vehicle = data.entity
 | |
|     if DoesEntityExist(vehicle) and IsEntityAVehicle(vehicle) then
 | |
|         showVehicleControlMenu(vehicle) -- Leite zum neuen Hauptmenü weiter
 | |
|     else
 | |
|         QBCore.Functions.Notify('Kein gültiges Fahrzeug gefunden!', 'error')
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Lade Anker beim Start
 | |
| CreateThread(function()
 | |
|     Wait(2000) -- Warte bis QBCore geladen ist
 | |
|     loadAnchoredBoats()
 | |
| end)
 | |
| 
 | |
| -- Cleanup verankerte Boote bei Resource Stop
 | |
| AddEventHandler('onResourceStop', function(resourceName)
 | |
|     if GetCurrentResourceName() == resourceName then
 | |
|         -- Alle verankerten Boote wieder freigeben (aber nicht vom Server löschen)
 | |
|         for vehicleNetId, _ in pairs(anchoredBoats) do
 | |
|             local vehicle = NetworkGetEntityFromNetworkId(vehicleNetId)
 | |
|             if DoesEntityExist(vehicle) then
 | |
|                 FreezeEntityPosition(vehicle, false)
 | |
|                 SetEntityInvincible(vehicle, false)
 | |
|             end
 | |
|         end
 | |
|         anchoredBoats = {}
 | |
|         currentVehicle = nil
 | |
|     end
 | |
| end)
 | |
| 
 | |
| -- Keybind für schnellen Zugriff
 | |
| RegisterCommand('carmenu', function()
 | |
|     local playerPed = PlayerPedId()
 | |
|     local vehicle = GetVehiclePedIsIn(playerPed, false)
 | |
|     
 | |
|     if vehicle ~= 0 then
 | |
|         showVehicleControlMenu(vehicle)
 | |
|     else
 | |
|         local coords = GetEntityCoords(playerPed)
 | |
|         local closestVehicle = GetClosestVehicle(coords.x, coords.y, coords.z, 5.0, 0, 71)
 | |
|         
 | |
|         if DoesEntityExist(closestVehicle) then
 | |
|             showVehicleControlMenu(closestVehicle)
 | |
|         else
 | |
|             QBCore.Functions.Notify('Kein Fahrzeug in der Nähe!', 'error')
 | |
|         end
 | |
|     end
 | |
| end, false)
 | |
| 
 | |
| -- Schneller Anker-Befehl für Kapitäne
 | |
| RegisterCommand('anchor', function()
 | |
|     local playerPed = PlayerPedId()
 | |
|     local vehicle = GetVehiclePedIsIn(playerPed, false)
 | |
|     
 | |
|     if vehicle ~= 0 and isBoat(vehicle) then
 | |
|         local currentSeat = getCurrentSeat(playerPed, vehicle)
 | |
|         if currentSeat == -1 then -- Nur Kapitän
 | |
|             toggleAnchor(vehicle)
 | |
|         else
 | |
|             QBCore.Functions.Notify('Nur der Kapitän kann den Anker bedienen!', 'error')
 | |
|         end
 | |
|     else
 | |
|         QBCore.Functions.Notify('Du musst Kapitän eines Bootes sein!', 'error')
 | |
|     end
 | |
| end, false)
 | |
| 
 | |
| -- Keybinds registrieren
 | |
| RegisterKeyMapping('carmenu', 'Fahrzeug Menü öffnen', 'keyboard', 'F1')
 | |
| RegisterKeyMapping('anchor', 'Anker werfen/lichten', 'keyboard', 'H')
 | |
| 
 | 
