local QBCore = exports['qb-core']:GetCoreObject() local spawnedNPCs = {} local activeRentalVehicles = {} -- NPCs spawnen CreateThread(function() -- Warte kurz, bis die Welt geladen ist Wait(2000) for i = 1, #Config.RentalLocations do local location = Config.RentalLocations[i] local modelHash = GetHashKey(location.npc.model) -- Modell laden RequestModel(modelHash) local timeout = 0 while not HasModelLoaded(modelHash) and timeout < 30 do Wait(100) timeout = timeout + 1 end if HasModelLoaded(modelHash) then -- NPC erstellen local coords = location.npc.coords local npc = CreatePed(4, modelHash, coords.x, coords.y, coords.z - 1.0, coords.w, false, false) -- NPC-Eigenschaften setzen FreezeEntityPosition(npc, true) SetEntityInvincible(npc, true) SetBlockingOfNonTemporaryEvents(npc, true) SetPedDefaultComponentVariation(npc) -- NPC speichern spawnedNPCs[location.id] = npc -- Debug-Info print("NPC spawned at location: " .. location.name) -- QB-Target für NPC exports['qb-target']:AddTargetEntity(npc, { options = { { type = "client", event = "vehiclerental:client:openMenu", icon = "fas fa-car", label = "Fahrzeug mieten", locationId = location.id }, { type = "client", event = "vehiclerental:client:returnVehicle", icon = "fas fa-car-side", label = "Fahrzeug zurückgeben", locationId = location.id } }, distance = 2.0 }) else print("Failed to load NPC model for location: " .. location.name) end end end) -- Mietmenü öffnen RegisterNetEvent('vehiclerental:client:openMenu', function(data) local locationId = data.locationId local location = nil for i = 1, #Config.RentalLocations do if Config.RentalLocations[i].id == locationId then location = Config.RentalLocations[i] break end end if not location then return end local options = {} for i = 1, #location.vehicles do local vehicle = location.vehicles[i] table.insert(options, { title = vehicle.label, description = '$' .. vehicle.price .. ' pro Stunde', icon = 'car', onSelect = function() openRentalDialog(vehicle, location) end }) end lib.registerContext({ id = 'vehicle_rental_menu', title = location.name, options = options }) lib.showContext('vehicle_rental_menu') end) -- Mietdialog function openRentalDialog(vehicle, location) local input = lib.inputDialog('Fahrzeug mieten', { { type = 'number', label = 'Mietdauer (Stunden)', description = 'Maximale Mietdauer: ' .. Config.MaxRentalTime .. ' Stunden', required = true, min = 1, max = Config.MaxRentalTime } }) if not input or not input[1] then return end local hours = tonumber(input[1]) if not hours or hours < 1 or hours > Config.MaxRentalTime then QBCore.Functions.Notify('Ungültige Mietdauer!', 'error') return end local totalCost = vehicle.price * hours local plate = GeneratePlate() QBCore.Functions.TriggerCallback('vehiclerental:server:rentVehicle', function(success) if success then spawnRentalVehicle(vehicle.model, location.spawnPoint, plate) end end, { vehicleModel = vehicle.model, pricePerHour = vehicle.price, hours = hours, locationId = location.id, plate = plate }) end -- Fahrzeug spawnen function spawnRentalVehicle(model, spawnPoint, plate) RequestModel(model) while not HasModelLoaded(model) do Wait(1) end local vehicle = CreateVehicle(model, spawnPoint.x, spawnPoint.y, spawnPoint.z, spawnPoint.w, true, false) SetVehicleNumberPlateText(vehicle, plate) SetEntityAsMissionEntity(vehicle, true, true) TaskWarpPedIntoVehicle(PlayerPedId(), vehicle, -1) -- Die Schlüssel werden jetzt serverseitig über MrNewbVehicleKeys verwaltet SetModelAsNoLongerNeeded(model) -- Speichere das Fahrzeug lokal activeRentalVehicles[plate] = vehicle -- Speichere die initiale Position local pos = GetEntityCoords(vehicle) local rot = GetEntityRotation(vehicle) TriggerServerEvent('vehiclerental:server:updatePosition', plate, pos, rot) print("[VehicleRental] Neues Mietfahrzeug erstellt: " .. plate) end -- Fahrzeug zurückgeben RegisterNetEvent('vehiclerental:client:returnVehicle', function(data) -- Hole alle aktiven Mietverhältnisse des Spielers QBCore.Functions.TriggerCallback('vehiclerental:server:getPlayerRentals', function(rentals) if not rentals or #rentals == 0 then QBCore.Functions.Notify('Du hast keine aktiven Mietverhältnisse!', 'error') return end -- Erstelle Menü mit allen gemieteten Fahrzeugen local options = {} for i = 1, #rentals do local rental = rentals[i] table.insert(options, { title = rental.vehicle_model .. " - " .. rental.vehicle_plate, description = "Zurückgeben " .. rental.timeText, icon = 'car', onSelect = function() returnSpecificVehicle(rental.vehicle_plate, data.locationId) end }) end lib.registerContext({ id = 'return_vehicle_menu', title = 'Fahrzeug zurückgeben', options = options }) lib.showContext('return_vehicle_menu') end) end) -- Fahrzeug löschen (wie DV-Befehl) function DeleteRentalVehicle(vehicle) if DoesEntityExist(vehicle) then -- Standard FiveM-Methode zum Löschen von Fahrzeugen (wie DV-Befehl) SetEntityAsMissionEntity(vehicle, true, true) DeleteEntity(vehicle) if DoesEntityExist(vehicle) then -- Zweiter Versuch mit DeleteVehicle DeleteVehicle(vehicle) end -- Prüfe, ob das Fahrzeug wirklich gelöscht wurde if not DoesEntityExist(vehicle) then return true else -- Letzter Versuch mit NetworkFadeOutEntity NetworkFadeOutEntity(vehicle, true, true) Wait(500) DeleteEntity(vehicle) return not DoesEntityExist(vehicle) end end return true -- Fahrzeug existiert nicht, also gilt es als gelöscht end -- Spezifisches Fahrzeug zurückgeben function returnSpecificVehicle(plate, locationId) -- Finde die Location-Daten local location = nil for i = 1, #Config.RentalLocations do if Config.RentalLocations[i].id == locationId then location = Config.RentalLocations[i] break end end if not location then QBCore.Functions.Notify('Fehler beim Finden des Rückgabeorts!', 'error') return end -- Definiere den Rückgabeort local returnPoint = vector3(location.returnPoint.x, location.returnPoint.y, location.returnPoint.z) -- Finde das Fahrzeug in der Nähe local vehicle = nil local closestDistance = 50.0 -- Maximale Suchentfernung -- Suche nach dem Fahrzeug mit dem Kennzeichen for veh in EnumerateVehicles() do local vehPlate = GetVehicleNumberPlateText(veh) if string.gsub(vehPlate, "%s+", "") == string.gsub(plate, "%s+", "") then local vehPos = GetEntityCoords(veh) local distance = #(returnPoint - vehPos) if distance < closestDistance then vehicle = veh closestDistance = distance end end end if not vehicle then QBCore.Functions.Notify('Fahrzeug nicht in der Nähe des Rückgabeorts gefunden!', 'error') return end -- Prüfe ob das Fahrzeug nahe genug am Rückgabeort ist local vehPos = GetEntityCoords(vehicle) local distance = #(returnPoint - vehPos) if distance > Config.MaxReturnDistance then QBCore.Functions.Notify('Das Fahrzeug muss näher am Rückgabeort sein! (Max. ' .. Config.MaxReturnDistance .. 'm)', 'error') return end -- Fahrzeug zurückgeben QBCore.Functions.TriggerCallback('vehiclerental:server:returnVehicle', function(success) if success then -- Fahrzeug mit DV-ähnlicher Methode löschen if DeleteRentalVehicle(vehicle) then QBCore.Functions.Notify('Fahrzeug erfolgreich zurückgegeben!', 'success') else QBCore.Functions.Notify('Fahrzeug zurückgegeben, aber konnte nicht vollständig gelöscht werden.', 'warning') end -- Aus lokaler Tracking-Liste entfernen activeRentalVehicles[plate] = nil end end, plate) end -- Lade alle aktiven Mietfahrzeuge RegisterNetEvent('vehiclerental:client:loadRentals') AddEventHandler('vehiclerental:client:loadRentals', function(rentals) -- Lösche alle vorhandenen Mietfahrzeuge for plate, vehicle in pairs(activeRentalVehicles) do if DoesEntityExist(vehicle) then DeleteRentalVehicle(vehicle) end end activeRentalVehicles = {} -- Spawne alle aktiven Mietfahrzeuge for _, rental in ipairs(rentals) do -- Prüfe, ob das Fahrzeug dem aktuellen Spieler gehört local playerCitizenId = QBCore.Functions.GetPlayerData().citizenid if rental.citizenid == playerCitizenId then -- Spawne das Fahrzeug nur, wenn es eine Position hat if rental.posX ~= 0 or rental.posY ~= 0 or rental.posZ ~= 0 then Citizen.CreateThread(function() local model = rental.vehicle_model local plate = rental.vehicle_plate -- Lade das Modell RequestModel(model) while not HasModelLoaded(model) do Citizen.Wait(10) end -- Spawne das Fahrzeug local vehicle = CreateVehicle(model, rental.posX, rental.posY, rental.posZ, rental.rotZ or 0.0, true, false) -- Setze Eigenschaften SetVehicleNumberPlateText(vehicle, plate) SetEntityAsMissionEntity(vehicle, true, true) SetVehicleDoorsLocked(vehicle, 2) -- Abgeschlossen -- Setze Rotation SetEntityRotation(vehicle, rental.rotX or 0.0, rental.rotY or 0.0, rental.rotZ or 0.0, 2, true) -- Schlüssel werden jetzt über MrNewbVehicleKeys verwaltet -- Speichere das Fahrzeug lokal activeRentalVehicles[plate] = vehicle print("[VehicleRental] Mietfahrzeug geladen: " .. plate) end) end end end end) -- Fahrzeug zurückgegeben RegisterNetEvent('vehiclerental:client:vehicleReturned') AddEventHandler('vehiclerental:client:vehicleReturned', function(plate) -- Lösche das Fahrzeug, wenn es existiert if activeRentalVehicles[plate] then local vehicle = activeRentalVehicles[plate] DeleteRentalVehicle(vehicle) activeRentalVehicles[plate] = nil print("[VehicleRental] Mietfahrzeug gelöscht: " .. plate) end -- Suche nach dem Fahrzeug in der Welt anhand des Kennzeichens for veh in EnumerateVehicles() do local vehPlate = GetVehicleNumberPlateText(veh) if string.gsub(vehPlate, "%s+", "") == string.gsub(plate, "%s+", "") then DeleteRentalVehicle(veh) print("[VehicleRental] Zusätzliches Mietfahrzeug mit Kennzeichen gelöscht: " .. plate) break end end end) -- Regelmäßiges Update der Fahrzeugpositionen Citizen.CreateThread(function() while true do Citizen.Wait(30000) -- Alle 30 Sekunden -- Update alle aktiven Mietfahrzeuge for plate, vehicle in pairs(activeRentalVehicles) do if DoesEntityExist(vehicle) then local pos = GetEntityCoords(vehicle) local rot = GetEntityRotation(vehicle) TriggerServerEvent('vehiclerental:server:updatePosition', plate, pos, rot) end end end end) -- Wenn der Spieler das Fahrzeug verlässt, aktualisiere die Position Citizen.CreateThread(function() while true do Citizen.Wait(500) local playerPed = PlayerPedId() local vehicle = GetVehiclePedIsIn(playerPed, true) if vehicle ~= 0 then local plate = GetVehicleNumberPlateText(vehicle) -- Wenn es ein Mietfahrzeug ist und der Spieler es gerade verlassen hat if activeRentalVehicles[plate] and not IsPedInVehicle(playerPed, vehicle, false) then local pos = GetEntityCoords(vehicle) local rot = GetEntityRotation(vehicle) TriggerServerEvent('vehiclerental:server:updatePosition', plate, pos, rot) end end end end) -- Regelmäßige Überprüfung auf "Ghost"-Mietfahrzeuge Citizen.CreateThread(function() while true do Citizen.Wait(300000) -- Alle 5 Minuten -- Hole alle aktiven Mietverhältnisse des Spielers QBCore.Functions.TriggerCallback('vehiclerental:server:getPlayerRentals', function(rentals) if not rentals or #rentals == 0 then return end local playerPos = GetEntityCoords(PlayerPedId()) local plateList = {} -- Erstelle Liste der gemieteten Kennzeichen for _, rental in ipairs(rentals) do table.insert(plateList, rental.vehicle_plate) end -- Suche nach allen Fahrzeugen in der Nähe for veh in EnumerateVehicles() do local vehPlate = GetVehicleNumberPlateText(veh) local vehPos = GetEntityCoords(veh) -- Wenn das Fahrzeug nicht in der Liste der gemieteten Fahrzeuge ist und ein RENT-Kennzeichen hat if string.find(vehPlate, "RENT") and not activeRentalVehicles[vehPlate] then local isRented = false -- Prüfe, ob es in der Liste der gemieteten Fahrzeuge ist for _, plate in ipairs(plateList) do if string.gsub(vehPlate, "%s+", "") == string.gsub(plate, "%s+", "") then isRented = true activeRentalVehicles[plate] = veh -- Aktualisiere die lokale Tracking-Liste break end end -- Wenn es nicht gemietet ist, aber ein RENT-Kennzeichen hat, lösche es if not isRented and #(playerPos - vehPos) > 100.0 then print("[VehicleRental] Lösche Ghost-Mietfahrzeug: " .. vehPlate) DeleteRentalVehicle(veh) end end end end) end end) -- Fahrzeug-Enumerator function EnumerateVehicles() return coroutine.wrap(function() local handle, vehicle = FindFirstVehicle() local success repeat coroutine.yield(vehicle) success, vehicle = FindNextVehicle(handle) until not success EndFindVehicle(handle) end) end -- Kennzeichen generieren (RENT + 4 zufällige Zeichen) function GeneratePlate() local plate = "RENT" for i = 1, 4 do if math.random(1, 2) == 1 then plate = plate .. string.char(math.random(65, 90)) -- A-Z else plate = plate .. tostring(math.random(0, 9)) -- 0-9 end end return plate end -- Cleanup beim Ressourcen-Stop AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() ~= resourceName then return end for _, npc in pairs(spawnedNPCs) do if DoesEntityExist(npc) then DeleteEntity(npc) end end for plate, vehicle in pairs(activeRentalVehicles) do if DoesEntityExist(vehicle) then DeleteRentalVehicle(vehicle) end end end) -- Überprüfe Mietfahrzeug-Schlüssel beim Laden des Spielers RegisterNetEvent('QBCore:Client:OnPlayerLoaded') AddEventHandler('QBCore:Client:OnPlayerLoaded', function() TriggerServerEvent('vehiclerental:server:checkRentalKeys') end)