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 local function loadAnchoredBoats() QBCore.Functions.TriggerCallback('nordi_seats: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 SetEntityCoords(vehicle, anchorData.coords.x, anchorData.coords.y, anchorData.coords.z) 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_seats: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_seats: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 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') -- Effekt: Kurzes Wackeln beim Anker lichten CreateThread(function() local originalCoords = GetEntityCoords(vehicle) for i = 1, 10 do local offset = math.random(-10, 10) / 100 SetEntityCoords(vehicle, originalCoords.x + offset, originalCoords.y + offset, originalCoords.z) Wait(50) end SetEntityCoords(vehicle, originalCoords.x, originalCoords.y, originalCoords.z) end) else -- Anker werfen local coords = GetEntityCoords(vehicle) local heading = GetEntityHeading(vehicle) 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') -- Anker-Wurf Effekt CreateThread(function() -- Spiele Anker-Sound PlaySoundFromEntity(-1, "CHECKPOINT_PERFECT", vehicle, "HUD_MINI_GAME_SOUNDSET", 0, 0) -- Kleine Wellen-Animation for i = 1, 5 do local coords = GetEntityCoords(vehicle) local waterHeight = GetWaterHeight(coords.x, coords.y, coords.z) if waterHeight then local effect = "ent_sht_water" RequestNamedPtfxAsset(effect) while not HasNamedPtfxAssetLoaded(effect) do Wait(1) end for j = 1, 3 do local offsetX = math.random(-200, 200) / 100 local offsetY = math.random(-200, 200) / 100 UseParticleFxAssetNextCall(effect) StartParticleFxNonLoopedAtCoord("ent_sht_water", coords.x + offsetX, coords.y + offsetY, waterHeight, 0.0, 0.0, 0.0, 0.5, false, false, false) end end Wait(200) end end) 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_seats:server:getAnchorByPlate', function(anchorData) if anchorData then -- Setze Boot an gespeicherte Position SetEntityCoords(vehicle, anchorData.coords.x, anchorData.coords.y, anchorData.coords.z) 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 -- Hauptmenü für Sitzauswahl local 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 -- Fahrzeuginfo table.insert(options, { title = '📋 Fahrzeuginfo', description = 'Informationen über das Fahrzeug', icon = 'fas fa-info-circle', onSelect = function() showVehicleInfo(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 Sitzmenü', icon = 'fas fa-arrow-left', onSelect = function() showSeatMenu(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:openSeatMenu", icon = "fas fa-car", label = "Sitzplatz wählen", } }, distance = 3.0 }) end) -- Event Handler RegisterNetEvent('vehicle:openSeatMenu', function(data) local vehicle = data.entity if DoesEntityExist(vehicle) and IsEntityAVehicle(vehicle) then showSeatMenu(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('seats', function() local playerPed = PlayerPedId() local vehicle = GetVehiclePedIsIn(playerPed, false) if vehicle ~= 0 then showSeatMenu(vehicle) else local coords = GetEntityCoords(playerPed) local closestVehicle = GetClosestVehicle(coords.x, coords.y, coords.z, 5.0, 0, 71) if DoesEntityExist(closestVehicle) then showSeatMenu(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('seats', 'Sitzplatz Menü öffnen', 'keyboard', 'F1') RegisterKeyMapping('anchor', 'Anker werfen/lichten', 'keyboard', 'H')