forked from Simnation/Main
1005 lines
34 KiB
Lua
1005 lines
34 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] = "Deck 1",
|
|
[5] = "Deck 2",
|
|
[6] = "Deck 3",
|
|
[7] = "Deck 4",
|
|
[8] = "Deck 5",
|
|
[9] = "Deck 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 (korrigierte Version)
|
|
local function loadAnchoredBoats()
|
|
QBCore.Functions.TriggerCallback('nord_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
|
|
|
|
-- Anker setzen/entfernen (korrigierte Version)
|
|
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)
|
|
|
|
-- Finde die Wasseroberfläche unter dem Boot
|
|
local waterHeight = GetWaterHeightNoWaves(coords.x, coords.y, coords.z)
|
|
|
|
-- Wenn Wasser gefunden wurde, setze das Boot auf die Wasseroberfläche
|
|
if waterHeight ~= 0 then
|
|
SetEntityCoords(vehicle, coords.x, coords.y, waterHeight)
|
|
coords = GetEntityCoords(vehicle) -- Aktualisiere Koordinaten nach Positionsänderung
|
|
end
|
|
|
|
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')
|
|
end
|
|
end
|
|
|
|
-- Überwache gespawnte Fahrzeuge für Anker-Wiederherstellung (korrigierte Version)
|
|
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('nord_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 (korrigierte Version)
|
|
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 (korrigierte Version)
|
|
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ü (korrigierte Version)
|
|
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ü (korrigierte Version)
|
|
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...'
|
|
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
|
|
CreateThread(function()
|
|
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)
|
|
|
|
-- 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')
|